xPDT.psm1
data LocalizedData { ConvertFrom-StringData @' FileNotFound=File not found in the environment path AbsolutePathOrFileName=Absolute path or file name expected InvalidArgument=Invalid argument: '{0}' with value: '{1}' InvalidArgumentAndMessage={0} {1} ErrorStarting=Failure starting process matching path '{0}'. Message: {1} FailureWaitingForProcessesToStart=Failed to wait for processes to start ProcessStarted=Process matching path '{0}' started in process ID {1} ProcessAlreadyStarted=Process matching path '{0}' already started in process ID {1} '@ } Import-LocalizedData LocalizedData -filename xPDT.strings.psd1 function ThrowInvalidArgumentError { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $errorId, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $errorMessage ) $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument $exception = New-Object System.ArgumentException $errorMessage; $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null throw $errorRecord } function ResolvePath { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Path ) $Path = [Environment]::ExpandEnvironmentVariables($Path) if(IsRootedPath $Path) { if(!(Test-Path $Path -PathType Leaf)) { ThrowInvalidArgumentError "CannotFindRootedPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound) } return $Path } if([string]::IsNullOrEmpty($env:Path)) { ThrowInvalidArgumentError "EmptyEnvironmentPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound) } if((Split-Path $Path -Leaf) -ne $Path) { ThrowInvalidArgumentError "NotAbsolutePathOrFileName" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.AbsolutePathOrFileName) } foreach($rawSegment in $env:Path.Split(";")) { $segment = [Environment]::ExpandEnvironmentVariables($rawSegment) $segmentRooted = $false try { $segmentRooted=[IO.Path]::IsPathRooted($segment) } catch {} if(!$segmentRooted) { continue } $candidate = join-path $segment $Path if(Test-Path $candidate -PathType Leaf) { return $candidate } } ThrowInvalidArgumentError "CannotFindRelativePath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound) } function IsRootedPath { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Path ) try { return [IO.Path]::IsPathRooted($Path) } catch { ThrowInvalidArgumentError "CannotGetIsPathRooted" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $_.Exception.Message) } } function ExtractArguments { [CmdletBinding()] param ( [parameter(Mandatory = $true)] $functionBoundParameters, [parameter(Mandatory = $true)] [string[]] $argumentNames, [string[]] $newArgumentNames ) $returnValue=@{} for($i=0;$i -lt $argumentNames.Count;$i++) { $argumentName = $argumentNames[$i] if($newArgumentNames -eq $null) { $newArgumentName = $argumentName } else { $newArgumentName = $newArgumentNames[$i] } if($functionBoundParameters.ContainsKey($argumentName)) { $null = $returnValue.Add($newArgumentName,$functionBoundParameters[$argumentName]) } } return $returnValue } function CallPInvoke { $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; using System.ComponentModel; using System.IO; namespace Source { [SuppressUnmanagedCodeSecurity] 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"; [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern bool CloseHandle(IntPtr handle); [DllImport("advapi32.dll", 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 ); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] public static extern bool DuplicateTokenEx( IntPtr hExistingToken, Int32 dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, Int32 ImpersonationLevel, Int32 dwTokenType, ref IntPtr phNewToken ); [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern Boolean LogonUser( String lpszUserName, String lpszDomain, String lpszPassword, LogonType dwLogonType, LogonProvider dwLogonProvider, out IntPtr phToken ); [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool AdjustTokenPrivileges( IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen ); [DllImport("kernel32.dll", ExactSpelling = true)] internal static extern IntPtr GetCurrentProcess(); [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool OpenProcessToken( IntPtr h, int acc, ref IntPtr phtok ); [DllImport("advapi32.dll", SetLastError = true)] internal static extern bool LookupPrivilegeValue( string host, string name, ref long pluid ); public static void CreateProcessAsUser(string strCommand, string strDomain, string strName, string strPassword) { 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 { bResult = LogonUser( strName, strDomain, strPassword, LogonType.LOGON32_LOGON_BATCH, LogonProvider.LOGON32_PROVIDER_DEFAULT, out hToken ); if (!bResult) { throw new Win32Exception("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 #" + Marshal.GetLastWin32Error().ToString()); } IntPtr hproc = GetCurrentProcess(); IntPtr htok = IntPtr.Zero; bResult = OpenProcessToken( hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok ); if(!bResult) { throw new Win32Exception("Open process token error #" + 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) { throw new Win32Exception("Error in looking up privilege of the process. This should not happen if DSC is running as LocalSystem Lookup privilege error #" + Marshal.GetLastWin32Error().ToString()); } bResult = AdjustTokenPrivileges( htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero ); if(!bResult) { throw new Win32Exception("Token elevation error #" + Marshal.GetLastWin32Error().ToString()); } bResult = DuplicateTokenEx( hToken, GENERIC_ALL_ACCESS, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hDupedToken ); if(!bResult) { throw new Win32Exception("Duplicate Token error #" + 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) { throw new Win32Exception("The process could not be created. Create process as user error #" + 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); } } } } } "@ Add-Type -TypeDefinition $ProgramSource -ReferencedAssemblies "System.ServiceProcess" } function GetWin32Process { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Path, [String] $Arguments, [PSCredential] $Credential ) $fileName = [io.path]::GetFileNameWithoutExtension($Path) $GetProcesses = @(Get-Process -Name $fileName -ErrorAction SilentlyContinue) $Processes = foreach($process in $GetProcesses) { if($Process.Path -ieq $Path) { try { [wmi]"Win32_Process.Handle='$($Process.Id)'" } catch { } } } if($PSBoundParameters.ContainsKey('Credential')) { $Processes = $Processes | Where-Object {(GetWin32ProcessOwner $_) -eq $Credential.UserName} } if($Arguments -eq $null) {$Arguments = ""} $Processes = $Processes | Where-Object {(GetWin32ProcessArgumentsFromCommandLine $_.CommandLine) -eq $Arguments} return $Processes } function GetWin32ProcessOwner { param ( [parameter(Mandatory = $true)] [ValidateNotNull()] $Process ) try { $Owner = $Process.GetOwner() } catch {} if($owner.Domain -ne $null) { return $Owner.Domain + "\" + $Owner.User } else { return $Owner.User } } function GetWin32ProcessArgumentsFromCommandLine { param ( [String] $commandLine ) if($commandLine -eq $null) { return "" } $commandLine=$commandLine.Trim() if($commandLine.Length -eq 0) { return "" } if($commandLine[0] -eq '"') { $charToLookfor=[char]'"' } else { $charToLookfor=[char]' ' } $endOfCommand=$commandLine.IndexOf($charToLookfor ,1) if($endOfCommand -eq -1) { return "" } return $commandLine.Substring($endOfCommand+1).Trim() } function StartWin32Process { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Path, [String] $Arguments, [PSCredential] $Credential, [Switch] $AsTask ) $GetArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential") $Processes = @(GetWin32Process @getArguments) if ($processes.Count -eq 0) { if($PSBoundParameters.ContainsKey("Credential")) { if($AsTask) { $ActionArguments = ExtractArguments $PSBoundParameters ` ("Path", "Arguments") ` ("Execute", "Argument") if([string]::IsNullOrEmpty($Arguments)) { $null = $ActionArguments.Remove("Argument") } $TaskGuid = [guid]::NewGuid().ToString() $Action = New-ScheduledTaskAction @ActionArguments $null = Register-ScheduledTask -TaskName "xPDT $TaskGuid" -Action $Action -User $Credential.UserName -Password $Credential.GetNetworkCredential().Password -RunLevel Highest $err = Start-ScheduledTask -TaskName "xPDT $TaskGuid" } else { try { CallPInvoke [Source.NativeMethods]::CreateProcessAsUser(("$Path " + $Arguments),$Credential.GetNetworkCredential().Domain,$Credential.GetNetworkCredential().UserName,$Credential.GetNetworkCredential().Password) } catch { $exception = New-Object System.ArgumentException $_ $errorCategory = [System.Management.Automation.ErrorCategory]::OperationStopped $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "Win32Exception", $errorCategory, $null $err = $errorRecord } } } else { $StartArguments = ExtractArguments $PSBoundParameters ` ("Path", "Arguments", "Credential") ` ("FilePath", "ArgumentList", "Credential") if([string]::IsNullOrEmpty($Arguments)) { $null = $StartArguments.Remove("ArgumentList") } $err = Start-Process @StartArguments } if($err -ne $null) { throw $err } if (!(WaitForWin32ProcessStart @GetArguments)) { ThrowInvalidArgumentError "FailureWaitingForProcessesToStart" ($LocalizedData.ErrorStarting -f $Path,$LocalizedData.FailureWaitingForProcessesToStart) } } else { return ($LocalizedData.ProcessAlreadyStarted -f $Path,$Processes.ProcessId) } $Processes = @(GetWin32Process @getArguments) return ($LocalizedData.ProcessStarted -f $Path,$Processes.ProcessId) } function WaitForWin32ProcessStart { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Path, [String] $Arguments, [PSCredential] $Credential ) $start = [DateTime]::Now $GetArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential") do { $value = @(GetWin32Process @GetArguments).Count -ge 1 } while(!$value -and ([DateTime]::Now - $start).TotalMilliseconds -lt 60000) return $value } function WaitForWin32ProcessEnd { [CmdletBinding()] param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [String] $Path, [String] $Arguments, [PSCredential] $Credential ) $GetArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential") While (WaitForWin32ProcessStart @GetArguments) { Start-Sleep 1 } Get-ScheduledTask | Where-Object {($_.TaskName.Length -ge 4) -and ($_.TaskName.Substring(0,4) -eq "xPDT") -and ($_.Actions.Execute -eq $Path) -and ($_.Actions.Arguments -eq $Arguments)} | Where-Object {$_ -ne $null} | Unregister-ScheduledTask -Confirm:$false } function NetUse { param ( [parameter(Mandatory)] [string] $SourcePath, [parameter(Mandatory)] [PSCredential] $Credential, [string] $Ensure = "Present" ) if(($SourcePath.Length -ge 2) -and ($SourcePath.Substring(0,2) -eq "\\")) { if ($Ensure -eq "Absent") { $cmd = "net.exe use $SourcePath /del" } else { $cmd = "net.exe use $SourcePath $($Credential.GetNetworkCredential().Password) /user:$($Credential.GetNetworkCredential().Domain)\$($Credential.GetNetworkCredential().UserName)" } Invoke-Expression $cmd } } Export-ModuleMember ResolvePath,StartWin32Process,WaitForWin32ProcessEnd,NetUse |