4 ConvertFrom-StringData @'
5 FileNotFound=File not found in the environment path.
6 AbsolutePathOrFileName=Absolute path or file name expected.
7 InvalidArgument=Invalid argument: '{0}' with value: '{1}'.
8 InvalidArgumentAndMessage={0} {1}
9 ProcessStarted=Process matching path '{0}' started
10 ProcessesStopped=Proceses matching path '{0}' with Ids '({1})' stopped.
11 ProcessAlreadyStarted=Process matching path '{0}' found running and no action required.
12 ProcessAlreadyStopped=Process matching path '{0}' not found running and no action required.
13 ErrorStopping=Failure stopping processes matching path '{0}' with IDs '({1})'. Message: {2}.
14 ErrorStarting=Failure starting process matching path '{0}'. Message: {1}.
15 StartingProcessWhatif=Start-Process
16 ProcessNotFound=Process matching path '{0}' not found
17 PathShouldBeAbsolute=The path should be absolute
18 PathShouldExist=The path should exist
19 ParameterShouldNotBeSpecified=Parameter {0} should not be specified.
20 FailureWaitingForProcessesToStart=Failed to wait for processes to start
21 FailureWaitingForProcessesToStop=Failed to wait for processes to stop
22 ErrorParametersNotSupportedWithCredential=Can't specify StandardOutputPath, StandardInputPath or WorkingDirectory when trying to run a process under a user context.
23 VerboseInProcessHandle=In process handle {0}
24 ErrorRunAsCredentialParameterNotSupported= The PsDscRunAsCredential parameter is not supported by the Process resource. To start the process with user '{0}', add the Credential parameter.
25 ErrorCredentialParameterNotSupportedWithRunAsCredential= The PsDscRunAsCredential parameter is not supported by the Process resource, and cannot be used with the Credential parameter. To start the process with user '{0}', use only the Credential parameter, not the PsDscRunAsCredential parameter.
29 Import-LocalizedData LocalizedData -filename MSFT_ProcessResource.strings.psd1
31 Import-Module "$PSScriptRoot\..\RunAsHelper.psm1"
33 function ExtractArguments($functionBoundParameters,[string[]]$argumentNames,[string[]]$newArgumentNames)
36 for($i=0;$i -lt $argumentNames.Count;$i++)
38 $argumentName=$argumentNames[$i]
40 if($newArgumentNames -eq $null)
42 $newArgumentName=$argumentName
46 $newArgumentName=$newArgumentNames[$i]
49 if($functionBoundParameters.ContainsKey($argumentName))
51 $null=$returnValue.Add($newArgumentName,$functionBoundParameters[$argumentName])
58 function IsRunFromLocalSystemUser()
60 (New-Object Security.Principal.WindowsPrincipal ( [Security.Principal.WindowsIdentity]::GetCurrent())).Identity.IsSystem
63 function Get-TargetResource
67 [parameter(Mandatory = $true)]
68 [ValidateNotNullOrEmpty()]
72 [parameter(Mandatory = $true)]
77 [ValidateNotNullOrEmpty()]
78 [System.Management.Automation.PSCredential]
82 $Path=(ResolvePath $Path)
83 $PSBoundParameters["Path"] = $Path
84 $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
85 $processes = @(GetWin32_Process @getArguments)
87 if($processes.Count -eq 0)
96 foreach($process in $processes)
98 # in case the process was killed between GetWin32_Process and this point, we should
99 # ignore errors which will generate empty entries in the return
100 $gpsProcess = (get-process -id $process.ProcessId -ErrorAction Ignore)
104 Arguments=(GetProcessArgumentsFromCommandLine $process.CommandLine)
105 PagedMemorySize=$gpsProcess.PagedMemorySize64
106 NonPagedMemorySize=$gpsProcess.NonpagedSystemMemorySize64
107 VirtualMemorySize=$gpsProcess.VirtualMemorySize64
108 HandleCount=$gpsProcess.HandleCount
110 ProcessId=$process.ProcessId
116 function Set-TargetResource
118 [CmdletBinding(SupportsShouldProcess=$true)]
121 [parameter(Mandatory = $true)]
122 [ValidateNotNullOrEmpty()]
126 [parameter(Mandatory = $true)]
131 [ValidateNotNullOrEmpty()]
132 [System.Management.Automation.PSCredential]
136 [ValidateSet("Present", "Absent")]
152 $Path=ResolvePath $Path
153 $PSBoundParameters["Path"] = $Path
154 $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
155 $processes = @(GetWin32_Process @getArguments)
157 if($Ensure -eq 'Absent')
159 "StandardOutputPath","StandardErrorPath","StandardInputPath","WorkingDirectory" | AssertParameterIsNotSpecified $PSBoundParameters
161 if ($processes.Count -gt 0)
163 $processIds=$processes.ProcessId
165 $err=Stop-Process -Id $processIds -force 2>&1
169 Write-Log ($LocalizedData.ProcessesStopped -f $Path,($processIds -join ","))
173 Write-Log ($LocalizedData.ErrorStopping -f $Path,($processIds -join ","),($err | out-string))
177 # Before returning from Set-TargetResource we have to ensure a subsequent Test-TargetResource is going to work
178 if (!(WaitForProcessCount @getArguments -waitCount 0))
180 $message = $LocalizedData.ErrorStopping -f $Path,($processIds -join ","),$LocalizedData.FailureWaitingForProcessesToStop
182 ThrowInvalidArgumentError "FailureWaitingForProcessesToStop" $message
187 Write-Log ($LocalizedData.ProcessAlreadyStopped -f $Path)
192 "StandardInputPath","WorkingDirectory" | AssertAbsolutePath $PSBoundParameters -Exist
193 "StandardOutputPath","StandardErrorPath" | AssertAbsolutePath $PSBoundParameters
195 if ($processes.Count -eq 0)
197 $startArguments = ExtractArguments $PSBoundParameters `
198 ("Path", "Arguments", "Credential", "StandardOutputPath", "StandardErrorPath", "StandardInputPath", "WorkingDirectory") `
199 ("FilePath", "ArgumentList", "Credential", "RedirectStandardOutput", "RedirectStandardError", "RedirectStandardInput", "WorkingDirectory")
201 if([string]::IsNullOrEmpty($Arguments))
203 $null=$startArguments.Remove("ArgumentList")
206 if($PSCmdlet.ShouldProcess($Path,$LocalizedData.StartingProcessWhatif))
209 # Start-Process calls .net Process.Start()
210 # If -Credential is present Process.Start() uses win32 api CreateProcessWithLogonW http://msdn.microsoft.com/en-us/library/0w4h05yb(v=vs.110).aspx
211 # CreateProcessWithLogonW cannot be called as LocalSystem user.
212 # Details http://msdn.microsoft.com/en-us/library/windows/desktop/ms682431(v=vs.85).aspx (section Remarks/Windows XP with SP2 and Windows Server 2003)
214 # In this case we call another api.
216 if($PSBoundParameters.ContainsKey("Credential") -and (IsRunFromLocalSystemUser))
218 if($PSBoundParameters.ContainsKey("StandardOutputPath") -or $PSBoundParameters.ContainsKey("StandardInputPath") -or $PSBoundParameters.ContainsKey("WorkingDirectory"))
220 $exception = New-Object System.ArgumentException $LocalizedData.ErrorParametersNotSupportedWithCredential
221 $err = New-Object System.Management.Automation.ErrorRecord $exception, "InvalidCombinationOfArguments", InvalidArgument, $null
225 $Domain, $UserName = Get-DomainAndUserName $Credential
229 # Internally we use win32 api LogonUser() with dwLogonType == LOGON32_LOGON_NETWORK_CLEARTEXT.
230 # It grants process ability for second-hop.
232 Import-DscNativeMethods
233 [PSDesiredStateConfiguration.NativeMethods]::CreateProcessAsUser( "$Path $Arguments", $Domain, $UserName, $Credential.Password, $false, [ref] $null )
237 throw New-Object System.Management.Automation.ErrorRecord $_.Exception, "Win32Exception", OperationStopped, $null
243 $err=Start-Process @startArguments 2>&1
247 Write-Log ($LocalizedData.ProcessStarted -f $Path)
251 Write-Log ($LocalizedData.ErrorStarting -f $Path,($err | Out-String))
255 # Before returning from Set-TargetResource we have to ensure a subsequent Test-TargetResource is going to work
256 if (!(WaitForProcessCount @getArguments -waitCount 1))
258 $message = $LocalizedData.ErrorStarting -f $Path,$LocalizedData.FailureWaitingForProcessesToStart
260 ThrowInvalidArgumentError "FailureWaitingForProcessesToStart" $message
266 Write-Log ($LocalizedData.ProcessAlreadyStarted -f $Path)
271 function Test-TargetResource
275 [parameter(Mandatory = $true)]
276 [ValidateNotNullOrEmpty()]
280 [parameter(Mandatory = $true)]
285 [ValidateNotNullOrEmpty()]
286 [System.Management.Automation.PSCredential]
290 [ValidateSet("Present", "Absent")]
306 if($PsDscContext.RunAsUser)
308 if($PSBoundParameters.ContainsKey("Credential"))
310 $exception = New-Object System.ArgumentException ($LocalizedData.ErrorCredentialParameterNotSupportedWithRunAsCredential -f $PsDscContext.RunAsUser)
311 $err = New-Object System.Management.Automation.ErrorRecord $exception, "InvalidArgument", InvalidArgument, $null
315 $exception = New-Object System.ArgumentException ($LocalizedData.ErrorRunAsCredentialParameterNotSupported -f $PsDscContext.RunAsUser)
316 $err = New-Object System.Management.Automation.ErrorRecord $exception, "InvalidCombinationOfArguments", InvalidArgument, $null
319 Write-Log ($LocalizedData.ErrorStarting -f $Path,($err | Out-String))
323 $Path=ResolvePath $Path
324 $PSBoundParameters["Path"] = $Path
325 $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
326 $processes = @(GetWin32_Process @getArguments)
329 if($Ensure -eq 'Absent')
331 return ($processes.Count -eq 0)
335 return ($processes.Count -gt 0)
339 function GetWin32ProcessOwner
343 [parameter(Mandatory = $true)]
348 # if the process was killed by the time this is called, GetOwner
349 # will throw a WMIMethodException "Not found"
352 $owner = Invoke-CimMethod -InputObject $process -MethodName GetOwner
358 if($owner.Domain -ne $null)
360 return $owner.Domain + "\" + $owner.User
368 function WaitForProcessCount
370 [CmdletBinding(SupportsShouldProcess=$true)]
373 [parameter(Mandatory = $true)]
374 [ValidateNotNullOrEmpty()]
381 [ValidateNotNullOrEmpty()]
382 [System.Management.Automation.PSCredential]
385 [parameter(Mandatory=$true)]
389 $start = [DateTime]::Now
392 $getArguments = ExtractArguments $PSBoundParameters ("Path","Arguments","Credential")
393 $value = @(GetWin32_Process @getArguments).Count -eq $waitCount
394 } while(!$value -and ([DateTime]::Now - $start).TotalMilliseconds -lt 2000)
399 function GetWin32_Process
401 [CmdletBinding(SupportsShouldProcess=$true)]
405 [parameter(Mandatory = $true)]
406 [ValidateNotNullOrEmpty()]
413 [ValidateNotNullOrEmpty()]
414 [System.Management.Automation.PSCredential]
422 $fileName = [io.path]::GetFileNameWithoutExtension($Path)
424 $gpsProcesses = @(get-process -Name $fileName -ErrorAction SilentlyContinue)
426 if($gpsProcesses.Count -ge $useWmiObjectCount)
428 # if there are many processes it is faster to perform a Get-WmiObject
429 # in order to get Win32_Process objects for all processes
430 $Path=WQLEscape $Path
431 $filter = "ExecutablePath = '$Path'"
432 $processes = Get-CimInstance Win32_Process -Filter $filter
436 # if there are few processes, building a Win32_Process for
437 # each matching result of get-process is faster
438 $processes = foreach($gpsProcess in $gpsProcesses)
440 if(!($gpsProcess.Path -ieq $Path))
447 Write-Verbose ($LocalizedData.VerboseInProcessHandle -f $gpsProcess.Id)
448 Get-CimInstance Win32_Process -Filter "ProcessId=$($gpsProcess.Id)"
452 #ignore if could not retrieve process
457 if($PSBoundParameters.ContainsKey('Credential'))
459 $Domain, $UserName = Get-DomainAndUserName $Credential
460 # Since there are credentials we need to call the GetOwner method in each process to search for matches
461 $processes = $processes | where { (GetWin32ProcessOwner $_) -eq "$Domain\$UserName" }
465 if($Arguments -eq $null) {$Arguments = ""}
466 $processes = $processes | where { (GetProcessArgumentsFromCommandLine $_.CommandLine) -eq $Arguments }
473 Strips the Arguments part of a commandLine. In "c:\temp\a.exe X Y Z" the Arguments part is "X Y Z".
475 function GetProcessArgumentsFromCommandLine
483 if($commandLine -eq $null)
488 $commandLine=$commandLine.Trim()
490 if($commandLine.Length -eq 0)
495 if($commandLine[0] -eq '"')
497 $charToLookfor=[char]'"'
501 $charToLookfor=[char]' '
504 $endofCommand=$commandLine.IndexOf($charToLookfor ,1)
505 if($endofCommand -eq -1)
510 return $commandLine.Substring($endofCommand+1).Trim()
515 Escapes a string to be used in a WQL filter as the one passed to get-wmiobject
522 [parameter(Mandatory = $true)]
523 [ValidateNotNullOrEmpty()]
528 return $query.Replace("\","\\").Replace('"','\"').Replace("'","\'")
531 function ThrowInvalidArgumentError
537 [parameter(Mandatory = $true)]
538 [ValidateNotNullOrEmpty()]
542 [parameter(Mandatory = $true)]
543 [ValidateNotNullOrEmpty()]
548 $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument
549 $exception = New-Object System.ArgumentException $errorMessage;
550 $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null
559 [parameter(Mandatory = $true)]
560 [ValidateNotNullOrEmpty()]
565 $Path = [Environment]::ExpandEnvironmentVariables($Path)
567 if(IsRootedPath $Path)
569 if(!(Test-Path $Path -PathType Leaf))
571 ThrowInvalidArgumentError "CannotFindRootedPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound)
577 if([string]::IsNullOrEmpty($env:Path))
579 ThrowInvalidArgumentError "EmptyEnvironmentPath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound)
582 # This will block relative paths. The statement is only true id $Path contains a plain file name.
583 # Checking a relative path against segments of the $env:Path does not make sense
584 if((Split-Path $Path -Leaf) -ne $Path)
586 ThrowInvalidArgumentError "NotAbsolutePathOrFileName" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.AbsolutePathOrFileName)
589 foreach($rawSegment in $env:Path.Split(";"))
591 $segment = [Environment]::ExpandEnvironmentVariables($rawSegment)
593 # if an exception causes $segmentedRooted not to be set, we will consider it $false
594 $segmentRooted = $false
597 # If the whole path passed through [IO.Path]::IsPathRooted with no exceptions, it does not have
598 # invalid characters, so segment has no invalid characters and will not throw as well
599 $segmentRooted=[IO.Path]::IsPathRooted($segment)
608 $candidate = join-path $segment $Path
610 if(Test-Path $candidate -PathType Leaf)
616 ThrowInvalidArgumentError "CannotFindRelativePath" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $LocalizedData.FileNotFound)
620 function AssertAbsolutePath
625 $ParentBoundParameters,
628 [Parameter (ValueFromPipeline=$true)]
637 if(!$ParentBoundParameters.ContainsKey($ParameterName))
642 $path=$ParentBoundParameters[$ParameterName]
644 if(!(IsRootedPath $Path))
646 ThrowInvalidArgumentError "PathShouldBeAbsolute" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f $ParameterName,$Path),
647 $LocalizedData.PathShouldBeAbsolute)
650 if(!$Exist.IsPresent)
655 if(!(Test-Path $Path))
657 ThrowInvalidArgumentError "PathShouldExist" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f $ParameterName,$Path),
658 $LocalizedData.PathShouldExist)
663 function AssertParameterIsNotSpecified
668 $ParentBoundParameters,
671 [Parameter (ValueFromPipeline=$true)]
677 if($ParentBoundParameters.ContainsKey($ParameterName))
679 ThrowInvalidArgumentError "ParameterShouldNotBeSpecified" ($LocalizedData.ParameterShouldNotBeSpecified -f $ParameterName)
684 function IsRootedPath
688 [parameter(Mandatory = $true)]
689 [ValidateNotNullOrEmpty()]
696 return [IO.Path]::IsPathRooted($Path)
700 # if the Path has invalid characters like >, <, etc, we cannot determine if it is rooted so we do not go on
701 ThrowInvalidArgumentError "CannotGetIsPathRooted" ($LocalizedData.InvalidArgumentAndMessage -f ($LocalizedData.InvalidArgument -f "Path",$Path), $_.Exception.Message)
707 [CmdletBinding(SupportsShouldProcess=$true)]
710 [parameter(Mandatory = $true)]
711 [ValidateNotNullOrEmpty()]
716 if ($PSCmdlet.ShouldProcess($Message, $null, $null))
718 Write-Verbose $Message
722 Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource