########################################################################### # built-in DSCResources common functions for Credentials and RunAs support ########################################################################### Set-StrictMode -version Latest Import-Module "$PSScriptRoot\DSCResourceHelper.psm1" data LocalizedData { # culture="en-US" ConvertFrom-StringData @' ErrorInvalidUserName=UserName {0} is not valid. UserCouldNotBeLoggedError=The user could not be logged on. Ensure that the user has an existing profile on the machine and that correct credentials are provided. Logon error # OpenProcessTokenError=Open process token error # PrivilegeLookingUpError=Error in looking up privilege of the process. This should not happen if DSC is running as LocalSystem Lookup privilege error # TokenElevationError=Token elevation error # DuplicateTokenError=Duplicate Token error # CouldNotCreateProcessError=The process could not be created. Create process as user error # WaitFailedError=Wait during create process failed user error # RetriveStatusError=Retrieving status error # '@ } Import-LocalizedData LocalizedData -filename RunAsHelper.strings.psd1 $EscapedLocalizedData = @{} foreach( $key in $LocalizedData.Keys ) { $EscapedLocalizedData.Add($key, $LocalizedData[$key].Replace('"','""')) } $LocalizedData = $EscapedLocalizedData # # The goal of this function is to get domain and username from PSCredential # without calling GetNetworkCredential() method. # Call to GetNetworkCredential() expose password as a plain text in memory. # function Get-DomainAndUserName([PSCredential]$Credential) { # # Supported formats: DOMAIN\username, username@domain # $wrongFormat = $false if ($Credential.UserName.Contains('\')) { $segments = $Credential.UserName.Split('\') if ($segments.Length -gt 2) { # i.e. domain\user\foo $wrongFormat = $true } else { $Domain = $segments[0] $UserName = $segments[1] } } elseif ($Credential.UserName.Contains('@')) { $segments = $Credential.UserName.Split('@') if ($segments.Length -gt 2) { # i.e. user@domain@foo $wrongFormat = $true } else { $UserName = $segments[0] $Domain = $segments[1] } } else { # support for default domain (localhost) return @( $env:COMPUTERNAME, $Credential.UserName ) } if ($wrongFormat) { $message = $LocalizedData.ErrorInvalidUserName -f $Credential.UserName Write-Verbose $message $exception = New-Object System.ArgumentException $message throw New-Object System.Management.Automation.ErrorRecord $exception, "InvalidUserName", InvalidArgument, $null } return @( $Domain, $UserName ) } function Import-DscNativeMethods { $script:ProgramSource = @" using System; using System.Collections.Generic; using System.Text; using System.Security; using System.Runtime.InteropServices; using System.Diagnostics; using System.Security.Principal; #if !CORECLR using System.ComponentModel; #endif using System.IO; namespace PSDesiredStateConfiguration { #if !CORECLR [SuppressUnmanagedCodeSecurity] #endif public static class NativeMethods { //The following structs and enums are used by the various Win32 API's that are used in the code below [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public Int32 cb; public string lpReserved; public string lpDesktop; public string lpTitle; public Int32 dwX; public Int32 dwY; public Int32 dwXSize; public Int32 dwXCountChars; public Int32 dwYCountChars; public Int32 dwFillAttribute; public Int32 dwFlags; public Int16 wShowWindow; public Int16 cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public Int32 dwProcessID; public Int32 dwThreadID; } [Flags] public enum LogonType { LOGON32_LOGON_INTERACTIVE = 2, LOGON32_LOGON_NETWORK = 3, LOGON32_LOGON_BATCH = 4, LOGON32_LOGON_SERVICE = 5, LOGON32_LOGON_UNLOCK = 7, LOGON32_LOGON_NETWORK_CLEARTEXT = 8, LOGON32_LOGON_NEW_CREDENTIALS = 9 } [Flags] public enum LogonProvider { LOGON32_PROVIDER_DEFAULT = 0, LOGON32_PROVIDER_WINNT35, LOGON32_PROVIDER_WINNT40, LOGON32_PROVIDER_WINNT50 } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public Int32 Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } public enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous, SecurityIdentification, SecurityImpersonation, SecurityDelegation } public enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation } [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct TokPriv1Luid { public int Count; public long Luid; public int Attr; } public const int GENERIC_ALL_ACCESS = 0x10000000; public const int CREATE_NO_WINDOW = 0x08000000; internal const int SE_PRIVILEGE_ENABLED = 0x00000002; internal const int TOKEN_QUERY = 0x00000008; internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; internal const string SE_INCRASE_QUOTA = "SeIncreaseQuotaPrivilege"; #if CORECLR [DllImport("api-ms-win-core-handle-l1-1-0.dll", #else [DllImport("kernel32.dll", #endif EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool CloseHandle(IntPtr handle); #if CORECLR [DllImport("api-ms-win-core-processthreads-l1-1-2.dll", #else [DllImport("advapi32.dll", #endif EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public static extern bool CreateProcessAsUser( IntPtr hToken, string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, Int32 dwCreationFlags, IntPtr lpEnvrionment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation ); #if CORECLR [DllImport("api-ms-win-security-base-l1-1-0.dll", EntryPoint = "DuplicateTokenEx")] #else [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] #endif public static extern bool DuplicateTokenEx( IntPtr hExistingToken, Int32 dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, Int32 ImpersonationLevel, Int32 dwTokenType, ref IntPtr phNewToken ); #if CORECLR [DllImport("api-ms-win-security-logon-l1-1-1.dll", CharSet = CharSet.Unicode, SetLastError = true)] #else [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] #endif public static extern Boolean LogonUser( String lpszUserName, String lpszDomain, IntPtr lpszPassword, LogonType dwLogonType, LogonProvider dwLogonProvider, out IntPtr phToken ); #if CORECLR [DllImport("api-ms-win-security-base-l1-1-0.dll", ExactSpelling = true, SetLastError = true)] #else [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] #endif internal static extern bool AdjustTokenPrivileges( IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen ); #if CORECLR [DllImport("api-ms-win-downlevel-kernel32-l1-1-0.dll", ExactSpelling = true)] #else [DllImport("kernel32.dll", ExactSpelling = true)] #endif internal static extern IntPtr GetCurrentProcess(); #if CORECLR [DllImport("api-ms-win-downlevel-advapi32-l1-1-1.dll", ExactSpelling = true, SetLastError = true)] #else [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] #endif internal static extern bool OpenProcessToken( IntPtr h, int acc, ref IntPtr phtok ); #if CORECLR [DllImport("api-ms-win-downlevel-kernel32-l1-1-0.dll", ExactSpelling = true)] #else [DllImport("kernel32.dll", ExactSpelling = true)] #endif internal static extern int WaitForSingleObject( IntPtr h, int milliseconds ); #if CORECLR [DllImport("api-ms-win-downlevel-kernel32-l1-1-0.dll", ExactSpelling = true)] #else [DllImport("kernel32.dll", ExactSpelling = true)] #endif internal static extern bool GetExitCodeProcess( IntPtr h, out int exitcode ); #if CORECLR [DllImport("api-ms-win-downlevel-advapi32-l4-1-0.dll", SetLastError = true)] #else [DllImport("advapi32.dll", SetLastError = true)] #endif internal static extern bool LookupPrivilegeValue( string host, string name, ref long pluid ); internal static void ThrowException( string message ) { #if CORECLR throw new Exception(message); #else throw new Win32Exception(message); #endif } public static void CreateProcessAsUser(string strCommand, string strDomain, string strName, SecureString secureStringPassword, bool waitForExit, ref int ExitCode) { var hToken = IntPtr.Zero; var hDupedToken = IntPtr.Zero; TokPriv1Luid tp; var pi = new PROCESS_INFORMATION(); var sa = new SECURITY_ATTRIBUTES(); sa.Length = Marshal.SizeOf(sa); Boolean bResult = false; try { IntPtr unmanagedPassword = IntPtr.Zero; try { #if CORECLR unmanagedPassword = SecureStringMarshal.SecureStringToCoTaskMemUnicode(secureStringPassword); #else unmanagedPassword = Marshal.SecureStringToGlobalAllocUnicode(secureStringPassword); #endif bResult = LogonUser( strName, strDomain, unmanagedPassword, LogonType.LOGON32_LOGON_NETWORK_CLEARTEXT, LogonProvider.LOGON32_PROVIDER_DEFAULT, out hToken ); } finally { Marshal.ZeroFreeGlobalAllocUnicode(unmanagedPassword); } if (!bResult) { ThrowException(@"$($LocalizedData.UserCouldNotBeLoggedError)" + Marshal.GetLastWin32Error().ToString()); } IntPtr hproc = GetCurrentProcess(); IntPtr htok = IntPtr.Zero; bResult = OpenProcessToken( hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok ); if (!bResult) { ThrowException(@"$($LocalizedData.OpenProcessTokenError)" + Marshal.GetLastWin32Error().ToString()); } tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED; bResult = LookupPrivilegeValue( null, SE_INCRASE_QUOTA, ref tp.Luid ); if (!bResult) { ThrowException(@"$($LocalizedData.PrivilegeLookingUpError)" + Marshal.GetLastWin32Error().ToString()); } bResult = AdjustTokenPrivileges( htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero ); if (!bResult) { ThrowException(@"$($LocalizedData.TokenElevationError)" + Marshal.GetLastWin32Error().ToString()); } bResult = DuplicateTokenEx( hToken, GENERIC_ALL_ACCESS, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hDupedToken ); if (!bResult) { ThrowException(@"$($LocalizedData.DuplicateTokenError)" + Marshal.GetLastWin32Error().ToString()); } var si = new STARTUPINFO(); si.cb = Marshal.SizeOf(si); si.lpDesktop = ""; bResult = CreateProcessAsUser( hDupedToken, null, strCommand, ref sa, ref sa, false, 0, IntPtr.Zero, null, ref si, ref pi ); if (!bResult) { ThrowException(@"$($LocalizedData.CouldNotCreateProcessError)" + Marshal.GetLastWin32Error().ToString()); } if (waitForExit) { int status = WaitForSingleObject(pi.hProcess, -1); if(status == -1) { ThrowException(@"$($LocalizedData.WaitFailedError)" + Marshal.GetLastWin32Error().ToString()); } bResult = GetExitCodeProcess(pi.hProcess, out ExitCode); if(!bResult) { ThrowException(@"$($LocalizedData.RetriveStatusError)" + Marshal.GetLastWin32Error().ToString()); } } } finally { if (pi.hThread != IntPtr.Zero) { CloseHandle(pi.hThread); } if (pi.hProcess != IntPtr.Zero) { CloseHandle(pi.hProcess); } if (hDupedToken != IntPtr.Zero) { CloseHandle(hDupedToken); } } } } } "@ if(IsNanoServer) { $ProgramSource = "#define CORECLR`n" + $ProgramSource Add-Type -TypeDefinition $ProgramSource -ReferencedAssemblies System.Security.SecureString.dll } else { Add-Type -TypeDefinition $ProgramSource -ReferencedAssemblies "System.ServiceProcess" } } Export-ModuleMember -Function Get-DomainAndUserName,Import-DscNativeMethods,IsNanoServer