Solved

Run c# Code as Logged on User

Posted on 2009-07-07
5
919 Views
Last Modified: 2013-12-17
I'm using the Impersonate command to print a report as a domain user.

The problem is that if I use the Windows Service on another machine, I have to change the user that is impersonated.

How can I change the code to always run as the currently logged on user, so it uses their default printer.
namespace Tools
{
	#region Using directives.
	// ----------------------------------------------------------------------
 
	using System;
	using System.Security.Principal;
	using System.Runtime.InteropServices;
	using System.ComponentModel;
 
	// ----------------------------------------------------------------------
	#endregion
 
	/////////////////////////////////////////////////////////////////////////
 
	/// <summary>
	/// Impersonation of a user. Allows to execute code under another
	/// user context.
	/// Please note that the account that instantiates the Impersonator class
	/// needs to have the 'Act as part of operating system' privilege set.
	/// </summary>
	/// <remarks>	
	/// This class is based on the information in the Microsoft knowledge base
	/// article http://support.microsoft.com/default.aspx?scid=kb;en-us;Q306158
	/// 
	/// Encapsulate an instance into a using-directive like e.g.:
	/// 
	///		...
	///		using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) )
	///		{
	///			...
	///			[code that executes under the new context]
	///			...
	///		}
	///		...
	/// 
	/// Please contact the author Uwe Keim (mailto:uwe.keim@zeta-software.de)
	/// for questions regarding this class.
	/// </remarks>
	public class Impersonator :
		IDisposable
	{
		#region Public methods.
		// ------------------------------------------------------------------
 
		/// <summary>
		/// Constructor. Starts the impersonation with the given credentials.
		/// Please note that the account that instantiates the Impersonator class
		/// needs to have the 'Act as part of operating system' privilege set.
		/// </summary>
		/// <param name="userName">The name of the user to act as.</param>
		/// <param name="domainName">The domain name of the user to act as.</param>
		/// <param name="password">The password of the user to act as.</param>
		public Impersonator(
			string userName,
			string domainName,
			string password )
		{
			ImpersonateValidUser( userName, domainName, password );
		}
 
		// ------------------------------------------------------------------
		#endregion
 
		#region IDisposable member.
		// ------------------------------------------------------------------
 
		public void Dispose()
		{
			UndoImpersonation();
		}
 
		// ------------------------------------------------------------------
		#endregion
 
		#region P/Invoke.
		// ------------------------------------------------------------------
 
		[DllImport("advapi32.dll", SetLastError=true)]
		private static extern int LogonUser(
			string lpszUserName,
			string lpszDomain,
			string lpszPassword,
			int dwLogonType,
			int dwLogonProvider,
			ref IntPtr phToken);
		
		[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
		private static extern int DuplicateToken(
			IntPtr hToken,
			int impersonationLevel,
			ref IntPtr hNewToken);
 
		[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
		private static extern bool RevertToSelf();
 
		[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
		private static extern  bool CloseHandle(
			IntPtr handle);
 
		private const int LOGON32_LOGON_INTERACTIVE = 2;
		private const int LOGON32_PROVIDER_DEFAULT = 0;
 
		// ------------------------------------------------------------------
		#endregion
 
		#region Private member.
		// ------------------------------------------------------------------
 
		/// <summary>
		/// Does the actual impersonation.
		/// </summary>
		/// <param name="userName">The name of the user to act as.</param>
		/// <param name="domainName">The domain name of the user to act as.</param>
		/// <param name="password">The password of the user to act as.</param>
		private void ImpersonateValidUser(
			string userName, 
			string domain, 
			string password )
		{
			WindowsIdentity tempWindowsIdentity = null;
			IntPtr token = IntPtr.Zero;
			IntPtr tokenDuplicate = IntPtr.Zero;
 
			try
			{
				if ( RevertToSelf() )
				{
					if ( LogonUser(
						userName, 
						domain, 
						password, 
						LOGON32_LOGON_INTERACTIVE,
						LOGON32_PROVIDER_DEFAULT, 
						ref token ) != 0 )
					{
						if ( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 )
						{
							tempWindowsIdentity = new WindowsIdentity( tokenDuplicate );
							impersonationContext = tempWindowsIdentity.Impersonate();
						}
						else
						{
							throw new Win32Exception( Marshal.GetLastWin32Error() );
						}
					}
					else
					{
						throw new Win32Exception( Marshal.GetLastWin32Error() );
					}
				}
				else
				{
					throw new Win32Exception( Marshal.GetLastWin32Error() );
				}
			}
			finally
			{
				if ( token!= IntPtr.Zero )
				{
					CloseHandle( token );
				}
				if ( tokenDuplicate!=IntPtr.Zero )
				{
					CloseHandle( tokenDuplicate );
				}
			}
		}
 
		/// <summary>
		/// Reverts the impersonation.
		/// </summary>
		private void UndoImpersonation()
		{
			if ( impersonationContext!=null )
			{
				impersonationContext.Undo();
			}	
		}
 
		private WindowsImpersonationContext impersonationContext = null;
 
		// ------------------------------------------------------------------
		#endregion
	}
 
	/////////////////////////////////////////////////////////////////////////
}

Open in new window

0
Comment
Question by:wint100
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
  • 2
5 Comments
 
LVL 2

Expert Comment

by:Wegelin
ID: 24801748
Hi
When you need the token from the loggedin User take this
You not need the User and Password
I do this over the Process explorer
with my class you can Start a Process from a System Service with the Loggedin User
ProcessAsUser.Launch("cmdline")


for your Problem maybe you can this do so:

WindowsImpersonationContext ImpersonationCtx = WindowsIdentity.Impersonate(ProcessAsUser.GetUserToken);

//Do something under the context of the impersonated user.

 ImpersonationCtx.Undo();


the snippet is in VB.net you can easy translate to c#
http://www.carlosag.net/Tools/CodeTranslator/









Imports System.Runtime.InteropServices
Imports System.Security.Permissions
Imports System.Reflection
Public Class ProcessAsUser
    <StructLayout(LayoutKind.Sequential)> _
Friend Structure PROCESS_INFORMATION
        Public hProcess As IntPtr
        Public hThread As IntPtr
        Public dwProcessId As Int32
        Public dwThreadId As Int32
    End Structure
    <StructLayout(LayoutKind.Sequential)> _
    Friend Structure SECURITY_ATTRIBUTES
        Public nLength As Int32
        Public lpSecurityDescriptor As IntPtr
        Public bInheritHandle As Boolean
    End Structure
 
    <StructLayout(LayoutKind.Sequential)> _
    Public Structure STARTUPINFO
        Public cb As Int32
        Public lpReserved As String
        Public lpDesktop As String
        Public lpTitle As String
        Public dwX As Int32
        Public dwY As Int32
        Public dwXSize As Int32
        Public dwYSize As Int32
        Public dwXCountChars As Int32
        Public dwYCountChars As Int32
        Public dwFillAttribute As Int32
        Public dwFlags As Int32
        Public wShowWindow As Short
        Public cbReserved2 As Short
        Public lpReserved2 As IntPtr
        Public hStdInput As IntPtr
        Public hStdOutput As IntPtr
        Public hStdError As IntPtr
    End Structure
 
    Public Enum SECURITY_IMPERSONATION_LEVEL
        SecurityAnonymous
        SecurityIdentification
        SecurityImpersonation
        SecurityDelegation
    End Enum
    Public Enum TOKEN_TYPE
 
        TokenPrimary = 1
 
        TokenImpersonation
    End Enum
 
    Private Shared STANDARD_RIGHTS_REQUIRED As UInteger = 983040
 
    Private Shared STANDARD_RIGHTS_READ As UInteger = 131072
 
    Private Shared TOKEN_ASSIGN_PRIMARY As UInteger = 1
 
    Private Shared TOKEN_DUPLICATE As UInteger = 2
 
    Private Shared TOKEN_IMPERSONATE As UInteger = 4
 
    Private Shared TOKEN_QUERY As UInteger = 8
 
    Private Shared TOKEN_QUERY_SOURCE As UInteger = 16
 
    Private Shared TOKEN_ADJUST_PRIVILEGES As UInteger = 32
 
    Private Shared TOKEN_ADJUST_GROUPS As UInteger = 64
 
    Private Shared TOKEN_ADJUST_DEFAULT As UInteger = 128
 
    Private Shared TOKEN_ADJUST_SESSIONID As UInteger = 256
 
    Private Shared TOKEN_READ As UInteger = (STANDARD_RIGHTS_READ Or TOKEN_QUERY)
    Private Shared TOKEN_ALL_ACCESS As UInteger = (STANDARD_RIGHTS_REQUIRED _
                   Or (TOKEN_ASSIGN_PRIMARY _
                   Or (TOKEN_DUPLICATE _
                   Or (TOKEN_IMPERSONATE _
                   Or (TOKEN_QUERY _
                   Or (TOKEN_QUERY_SOURCE _
                   Or (TOKEN_ADJUST_PRIVILEGES _
                   Or (TOKEN_ADJUST_GROUPS _
                   Or (TOKEN_ADJUST_DEFAULT Or TOKEN_ADJUST_SESSIONID)))))))))
 
    ' Private Declare Auto Function CreateProcessAsUser Lib "advapi32.dll" (ByVal token As IntPtr, ByVal applicationName As String, ByVal commandLine As String, ByRef processAttributes As SECURITY_ATTRIBUTES, ByRef threadAttributes As SECURITY_ATTRIBUTES, ByVal inheritHandles As Boolean, ByVal creationFlags As UInteger, ByVal environment As IntPtr, ByVal currentDirectory As String, ByRef startupInfo As STARTUPINFO, ByRef processInformation As PROCESS_INFORMATION) As Boolean
    Private Declare Auto Function CreateProcessAsUser Lib "advapi32" ( _
      ByVal hToken As IntPtr, _
      ByVal strApplicationName As String, _
      ByVal strCommandLine As String, _
      ByRef lpProcessAttributes As SECURITY_ATTRIBUTES, _
      ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
      ByVal bInheritHandles As Boolean, _
      ByVal dwCreationFlags As Integer, _
      ByVal lpEnvironment As IntPtr, _
      ByVal lpCurrentDriectory As String, _
      ByRef lpStartupInfo As STARTUPINFO, _
      ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean
 
    Public Declare Function DuplicateTokenEx Lib "advapi32.dll" (ByVal existingToken As IntPtr, ByVal desiredAccess As UInteger, ByVal tokenAttributes As IntPtr, ByVal impersonationLevel As SECURITY_IMPERSONATION_LEVEL, ByVal tokenType As TOKEN_TYPE, ByRef newToken As IntPtr) As Boolean
    Declare Function CreateEnvironmentBlock Lib "userenv.dll" (ByRef lpEnvironment As IntPtr, ByVal hToken As IntPtr, ByVal bInherit As Boolean) As Boolean
    Declare Function OpenProcessToken Lib "advapi32.dll" (ByVal ProcessHandle As IntPtr, ByVal DesiredAccess As Integer, ByRef TokenHandle As IntPtr) As Boolean
    Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal handle As IntPtr) As Boolean
 
    Declare Function DestroyEnvironmentBlock Lib "userenv.dll" (ByVal lpEnvironment As IntPtr) As Boolean
    Private Const SW_SHOW As Short = 5
    Private Const SW_SHOWMAXIMIZED As Short = 7
    ' Private Const TOKEN_QUERY = 8
    'Private Const TOKEN_DUPLICATE = 2
    'Private Const TOKEN_ASSIGN_PRIMARY = 1
    Private Const GENERIC_ALL_ACCESS = 268435456
    Private Const STARTF_USESHOWWINDOW = 1
    Private Const STARTF_FORCEONFEEDBACK = 64
    Private Const CREATE_UNICODE_ENVIRONMENT = 1024
    Public Shared ErrStr As String = ""
 
    Private Shared Function LaunchProcessAsUser(ByVal cmdLine As String, ByVal _
      token As IntPtr, ByVal envBlock As IntPtr) As Boolean
        Dim result As Boolean = False
        Dim pi As PROCESS_INFORMATION = New PROCESS_INFORMATION
        Dim saProcess As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES
        Dim saThread As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES
 
        saProcess.nLength = Marshal.SizeOf(saProcess)
        saThread.nLength = Marshal.SizeOf(saThread)
        Dim si As STARTUPINFO = New STARTUPINFO
        si.cb = Marshal.SizeOf(si)
        'si.lpDesktop = "WinSta0\Default"
        si.lpDesktop = Nothing
        si.dwFlags = STARTF_USESHOWWINDOW Or STARTF_FORCEONFEEDBACK
        si.wShowWindow = SW_SHOW
        result = CreateProcessAsUser(token, cmdLine, "", saProcess, saThread, _
          False, CREATE_UNICODE_ENVIRONMENT, envBlock, Nothing, si, pi)
        If result = False Then
            Dim iError As Integer = Marshal.GetLastWin32Error
            ErrStr = String.Format("CreateProcessAsUser Error:" & _
              "{0}", iError)
            ' Debug.WriteLine(message)
        Else
            CloseHandle(token)
        End If
        Return result
    End Function
 
    Private Shared Function GetPrimaryToken(ByVal processId As Integer) As  _
      IntPtr
        Dim token As IntPtr = IntPtr.Zero
        Dim primaryToken As IntPtr = IntPtr.Zero
        Dim retVal As Boolean = False
        Dim p As Process = Nothing
        Try
            p = Process.GetProcessById(processId)
        Catch generatedExceptionVariable0 As ArgumentException
            Dim details As String = String.Format("ProcessID {0} Not" & _
              "Available", processId)
            Debug.WriteLine(details)
            Return primaryToken
        End Try
        retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, token)
        If retVal = True Then
            Dim sa As SECURITY_ATTRIBUTES = New SECURITY_ATTRIBUTES
            sa.nLength = Marshal.SizeOf(sa)
            retVal = DuplicateTokenEx(token, TOKEN_ASSIGN_PRIMARY Or _
              TOKEN_DUPLICATE Or TOKEN_QUERY, IntPtr.Zero, CType( _
              SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, Integer), _
              CType(TOKEN_TYPE.TokenPrimary, Integer), primaryToken)
            If retVal = False Then
                ErrStr = String.Format("DuplicateTokenEx Error:" & _
                  "{0}", Marshal.GetLastWin32Error)
 
                'Debug.WriteLine(message)
            End If
        Else
            ErrStr = String.Format("OpenProcessToken Error:" & _
              "{0}", Marshal.GetLastWin32Error)
            'Debug.WriteLine(message)
        End If
        Return primaryToken
    End Function
 
    Private Shared Function GetEnvironmentBlock(ByVal token As IntPtr) As IntPtr
        Dim envBlock As IntPtr = IntPtr.Zero
        Dim retVal As Boolean = CreateEnvironmentBlock(envBlock, token, False)
        If retVal = False Then
            Dim message As String = String.Format("CreateEnvironmentBlock" & _
              "Error: {0}", Marshal.GetLastWin32Error)
            ' Debug.WriteLine(message)
        End If
        Return envBlock
    End Function
 
    Public Shared Function Launch(ByVal appCmdLine As String) As Boolean
        Dim ret As Boolean = False
        Dim ps As Process() = Process.GetProcessesByName("explorer")
        Dim processId As Integer = -1
        If ps.Length > 0 Then
            processId = ps(0).Id
        End If
        If processId > 1 Then
            Dim token As IntPtr = GetPrimaryToken(processId)
            If Not (token.Equals(IntPtr.Zero)) Then
                Dim envBlock As IntPtr = GetEnvironmentBlock(token)
                ret = LaunchProcessAsUser(appCmdLine, token, envBlock)
                If Not (envBlock.Equals(IntPtr.Zero)) Then
                    DestroyEnvironmentBlock(envBlock)
                End If
            End If
        End If
        Return ret
    End Function
    Public Shared Function GetUserToken() As IntPtr
        ErrStr = ""
        Dim token As IntPtr
        Dim ps As Process() = Process.GetProcessesByName("explorer")
        Dim processId As Integer = -1
        If ps.Length > 0 Then
            processId = ps(0).Id
        End If
        If processId > 1 Then
            token = GetPrimaryToken(processId)
 
        End If
 
        Return token
    End Function
 
    Public Sub New()
        MyBase.New()
    End Sub
End Class

Open in new window

0
 
LVL 1

Author Comment

by:wint100
ID: 24833929
This doesn't really help i'm afraid. Is there a way to just run the impersonate code without sending the fixed user, pass, domain and instead just send the credentials of the logged in user?
0
 
LVL 2

Expert Comment

by:Wegelin
ID: 24838046

 My Code Works only  under the Computer where the Service is running.


With this Class you NOT need a USER or Domain Or Password!!!

With ProcessAsUser.GetUserToken you became the Token from The Logged In USER

So you can now Write your code so for printing in A Service with the logged in USER

in VB.NET

This code is as System

console.writeline("I Am SYSTEM")


 Dim impctx As WindowsImpersonationContext = WindowsIdentity.Impersonate(ProcessAsUser.GetUserToken)

           console.writeline("I Am USER")

            Dim pd As New System.Drawing.Printing.PrintDocument
            pd.DocumentName = "c:\temp\test.txt"
            pd.Print()

            impctx.Undo()

  console.writeline("I Am SYSTEM")












0
 
LVL 1

Author Comment

by:wint100
ID: 24838072
OK, I'll give it a go and post back.

Do I just need to use the code in your original post???
0
 
LVL 2

Accepted Solution

by:
Wegelin earned 500 total points
ID: 24838111
you only need to convert the ProccessAsUser Class to C#

and then do this

WindowsImpersonationContext impctx = WindowsIdentity.Impersonate(ProcessAsUser.GetUserToken);
System.Drawing.Printing.PrintDocument pd = new System.Drawing.Printing.PrintDocument();
pd.DocumentName = "c:\\temp\\test.txt";
pd.Print();
impctx.Undo();
0

Featured Post

PeopleSoft Has Never Been Easier

PeopleSoft Adoption Made Smooth & Simple!

On-The-Job Training Is made Intuitive & Easy With WalkMe's On-Screen Guidance Tool.  Claim Your Free WalkMe Account Now

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
need to convert function to c# 5 45
Making exceptions for ValidationAttribute 2 27
parsing question on dynamic concatenation 3 34
Need help with a query 14 40
This article is for Object-Oriented Programming (OOP) beginners. An Interface contains declarations of events, indexers, methods and/or properties. Any class which implements the Interface should provide the concrete implementation for each Inter…
Entity Framework is a powerful tool to help you interact with the DataBase but still doesn't help much when we have a Stored Procedure that returns more than one resultset. The solution takes some of out-of-the-box thinking; read on!
Attackers love to prey on accounts that have privileges. Reducing privileged accounts and protecting privileged accounts therefore is paramount. Users, groups, and service accounts need to be protected to help protect the entire Active Directory …

730 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question