mirror of
https://github.com/PowerShell/PSDscResources
synced 2026-06-21 13:45:29 +00:00
1515 lines
46 KiB
PowerShell
1515 lines
46 KiB
PowerShell
$errorActionPreference = 'Stop'
|
|
Set-StrictMode -Version 'Latest'
|
|
|
|
# Import CommonResourceHelper for Get-LocalizedData, New-InvalidOperationException, New-InvalidArgumentException
|
|
$script:dscResourcesFolderFilePath = Split-Path $PSScriptRoot -Parent
|
|
$script:commonResourceHelperFilePath = Join-Path -Path $script:dscResourcesFolderFilePath -ChildPath 'CommonResourceHelper.psm1'
|
|
Import-Module -Name $script:commonResourceHelperFilePath
|
|
|
|
# Localized messages for verbose and error statements in this resource
|
|
$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_WindowsProcess'
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the current state of the Windows process(es) with the specified
|
|
executable and arguments.
|
|
|
|
If more than one process is found, only the information of the first process is retrieved.
|
|
ProcessCount will contain the actual number of processes that were found.
|
|
|
|
.PARAMETER Path
|
|
The path to the process executable. If this is the file name of the executable
|
|
(not the fully qualified path), the DSC resource will search the environment Path variable
|
|
($env:Path) to find the executable file. If the value of this property is a fully qualified
|
|
path, DSC will use the given Path variable to find the file. If the path is not found it
|
|
will throw an error. Relative paths are not allowed.
|
|
|
|
.PARAMETER Arguments
|
|
The arguments to the process as a single string.
|
|
|
|
.PARAMETER Credential
|
|
The credential of the user account to start the process under.
|
|
#>
|
|
function Get-TargetResource
|
|
{
|
|
[OutputType([System.Collections.Hashtable])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$Path,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[AllowEmptyString()]
|
|
[System.String]
|
|
$Arguments,
|
|
|
|
[Parameter()]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Management.Automation.PSCredential]
|
|
[System.Management.Automation.Credential()]
|
|
$Credential
|
|
)
|
|
|
|
Write-Verbose -Message ($script:localizedData.GetTargetResourceStartMessage -f $Path)
|
|
|
|
$Path = Expand-Path -Path $Path
|
|
|
|
$getProcessCimInstanceArguments = @{
|
|
Path = $Path
|
|
Arguments = $Arguments
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('Credential'))
|
|
{
|
|
$getProcessCimInstanceArguments['Credential'] = $Credential
|
|
}
|
|
|
|
$processCimInstance = @( Get-ProcessCimInstance @getProcessCimInstanceArguments )
|
|
|
|
$processToReturn = @{}
|
|
|
|
if ($processCimInstance.Count -eq 0)
|
|
{
|
|
$processToReturn = @{
|
|
Path = $Path
|
|
Arguments = $Arguments
|
|
Ensure ='Absent'
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$processId = $processCimInstance[0].ProcessId
|
|
$getProcessResult = Get-Process -ID $processId
|
|
|
|
$processToReturn = @{
|
|
Path = $Path
|
|
Arguments = $Arguments
|
|
PagedMemorySize = $getProcessResult.PagedMemorySize64
|
|
NonPagedMemorySize = $getProcessResult.NonpagedSystemMemorySize64
|
|
VirtualMemorySize = $getProcessResult.VirtualMemorySize64
|
|
HandleCount = $getProcessResult.HandleCount
|
|
Ensure = 'Present'
|
|
ProcessId = $processId
|
|
ProcessCount = $processCimInstance.Count
|
|
}
|
|
}
|
|
|
|
Write-Verbose -Message ($script:localizedData.GetTargetResourceEndMessage -f $Path)
|
|
|
|
return $processToReturn
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Sets the Windows process with the specified executable path and arguments
|
|
to the specified state.
|
|
|
|
If multiple process are found, the specified state will be set for all of them.
|
|
|
|
.PARAMETER Path
|
|
The path to the process executable. If this is the file name of the executable
|
|
(not the fully qualified path), the DSC resource will search the environment Path variable
|
|
($env:Path) to find the executable file. If the value of this property is a fully qualified
|
|
path, DSC will use the given Path variable to find the file. If the path is not found it
|
|
will throw an error. Relative paths are not allowed.
|
|
|
|
.PARAMETER Arguments
|
|
The arguments to pass to the process as a single string.
|
|
|
|
.PARAMETER Credential
|
|
The credential of the user account to start the process under.
|
|
|
|
.PARAMETER Ensure
|
|
Specifies whether or not the process should exist.
|
|
To start or modify a process, set this property to Present.
|
|
To stop a process, set this property to Absent.
|
|
The default value is Present.
|
|
|
|
.PARAMETER StandardOutputPath
|
|
The file path to write the standard output to. Any existing file at this path
|
|
will be overwritten.This property cannot be specified at the same time as Credential
|
|
when running the process as a local user.
|
|
|
|
.PARAMETER StandardErrorPath
|
|
The file path to write the standard error output to. Any existing file at this path
|
|
will be overwritten.
|
|
|
|
.PARAMETER StandardInputPath
|
|
The file path to get standard input from. This property cannot be specified at the
|
|
same time as Credential when running the process as a local user.
|
|
|
|
.PARAMETER WorkingDirectory
|
|
The file path to use as the working directory for the process. Any existing file
|
|
at this path will be overwritten. This property cannot be specified at the same time
|
|
as Credential when running the process as a local user.
|
|
#>
|
|
function Set-TargetResource
|
|
{
|
|
[CmdletBinding(SupportsShouldProcess = $true)]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$Path,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[AllowEmptyString()]
|
|
[System.String]
|
|
$Arguments,
|
|
|
|
[Parameter()]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Management.Automation.PSCredential]
|
|
[System.Management.Automation.Credential()]
|
|
$Credential,
|
|
|
|
[Parameter()]
|
|
[ValidateSet('Present', 'Absent')]
|
|
[System.String]
|
|
$Ensure = 'Present',
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$StandardOutputPath,
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$StandardErrorPath,
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$StandardInputPath,
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$WorkingDirectory
|
|
)
|
|
|
|
Write-Verbose -Message ($script:localizedData.SetTargetResourceStartMessage -f $Path)
|
|
|
|
Assert-PsDscContextNotRunAsUser
|
|
|
|
$Path = Expand-Path -Path $Path
|
|
|
|
$getProcessCimInstanceArguments = @{
|
|
Path = $Path
|
|
Arguments = $Arguments
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('Credential'))
|
|
{
|
|
$getProcessCimInstanceArguments['Credential'] = $Credential
|
|
}
|
|
|
|
$processCimInstance = @( Get-ProcessCimInstance @getProcessCimInstanceArguments )
|
|
|
|
if ($Ensure -eq 'Absent')
|
|
{
|
|
$assertHashtableParams = @{
|
|
Hashtable = $PSBoundParameters
|
|
Key = @( 'StandardOutputPath',
|
|
'StandardErrorPath',
|
|
'StandardInputPath',
|
|
'WorkingDirectory' )
|
|
}
|
|
Assert-HashtableDoesNotContainKey @assertHashtableParams
|
|
|
|
$whatIfShouldProcess = $PSCmdlet.ShouldProcess($Path, $script:localizedData.StoppingProcessWhatif)
|
|
|
|
if ($processCimInstance.Count -gt 0 -and $whatIfShouldProcess)
|
|
{
|
|
# If there are multiple process Ids, all will be included to be stopped
|
|
$processIds = $processCimInstance.ProcessId
|
|
|
|
# Redirecting error output to standard output while we try to stop the processes
|
|
$stopProcessError = Stop-Process -Id $processIds -Force 2>&1
|
|
|
|
if ($null -eq $stopProcessError)
|
|
{
|
|
Write-Verbose -Message ($script:localizedData.ProcessesStopped -f $Path, ($processIds -join ','))
|
|
}
|
|
else
|
|
{
|
|
$errorMessage = ($script:localizedData.ErrorStopping -f $Path,
|
|
($processIds -join ','),
|
|
($stopProcessError | Out-String))
|
|
|
|
New-InvalidOperationException -Message $errorMessage
|
|
}
|
|
|
|
<#
|
|
Before returning from Set-TargetResource we have to ensure a subsequent
|
|
Test-TargetResource is going to work
|
|
#>
|
|
if (-not (Wait-ProcessCount -ProcessSettings $getProcessCimInstanceArguments -ProcessCount 0))
|
|
{
|
|
$message = $script:localizedData.ErrorStopping -f $Path, ($processIds -join ','),
|
|
$script:localizedData.FailureWaitingForProcessesToStop
|
|
|
|
New-InvalidOperationException -Message $message
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Write-Verbose -Message ($script:localizedData.ProcessAlreadyStopped -f $Path)
|
|
}
|
|
}
|
|
# Ensure = 'Present'
|
|
else
|
|
{
|
|
$shouldBeRootedPathArguments = @( 'StandardInputPath',
|
|
'WorkingDirectory',
|
|
'StandardOutputPath',
|
|
'StandardErrorPath' )
|
|
|
|
foreach ($shouldBeRootedPathArgument in $shouldBeRootedPathArguments)
|
|
{
|
|
if (-not [System.String]::IsNullOrEmpty($PSBoundParameters[$shouldBeRootedPathArgument]))
|
|
{
|
|
$assertPathArgumentRootedParams = @{
|
|
PathArgumentName = $shouldBeRootedPathArgument
|
|
PathArgument = $PSBoundParameters[$shouldBeRootedPathArgument]
|
|
}
|
|
Assert-PathArgumentRooted @assertPathArgumentRootedParams
|
|
}
|
|
}
|
|
|
|
$shouldExistPathArguments = @( 'StandardInputPath', 'WorkingDirectory' )
|
|
|
|
foreach ($shouldExistPathArgument in $shouldExistPathArguments)
|
|
{
|
|
if (-not [System.String]::IsNullOrEmpty($PSBoundParameters[$shouldExistPathArgument]))
|
|
{
|
|
$assertPathArgumentValidParams = @{
|
|
PathArgumentName = $shouldExistPathArgument
|
|
PathArgument = $PSBoundParameters[$shouldExistPathArgument]
|
|
}
|
|
Assert-PathArgumentValid @assertPathArgumentValidParams
|
|
}
|
|
}
|
|
|
|
if ($processCimInstance.Count -eq 0)
|
|
{
|
|
$startProcessArguments = @{
|
|
FilePath = $Path
|
|
}
|
|
|
|
$startProcessOptionalArgumentMap = @{
|
|
Credential = 'Credential'
|
|
RedirectStandardOutput = 'StandardOutputPath'
|
|
RedirectStandardError = 'StandardErrorPath'
|
|
RedirectStandardInput = 'StandardInputPath'
|
|
WorkingDirectory = 'WorkingDirectory'
|
|
}
|
|
|
|
foreach ($startProcessOptionalArgumentName in $startProcessOptionalArgumentMap.Keys)
|
|
{
|
|
$parameterKey = $startProcessOptionalArgumentMap[$startProcessOptionalArgumentName]
|
|
$parameterValue = $PSBoundParameters[$parameterKey]
|
|
|
|
if (-not [String]::IsNullOrEmpty($parameterValue))
|
|
{
|
|
$startProcessArguments[$startProcessOptionalArgumentName] = $parameterValue
|
|
}
|
|
}
|
|
|
|
if (-not [String]::IsNullOrEmpty($Arguments))
|
|
{
|
|
$startProcessArguments['ArgumentList'] = $Arguments
|
|
}
|
|
|
|
if ($PSCmdlet.ShouldProcess($Path, $script:localizedData.StartingProcessWhatif))
|
|
{
|
|
<#
|
|
Start-Process calls .net Process.Start()
|
|
If -Credential is present Process.Start() uses win32 api CreateProcessWithLogonW
|
|
http://msdn.microsoft.com/en-us/library/0w4h05yb(v=vs.110).aspx
|
|
CreateProcessWithLogonW cannot be called as LocalSystem user.
|
|
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)
|
|
|
|
In this case we call another api.
|
|
#>
|
|
if (($PSBoundParameters.ContainsKey('Credential')) -and (Test-IsRunFromLocalSystemUser))
|
|
{
|
|
# Throw an exception if any of the below parameters are included with Credential passed
|
|
foreach ($key in @('StandardOutputPath', 'StandardInputPath', 'WorkingDirectory'))
|
|
{
|
|
if ($PSBoundParameters.Keys -contains $key)
|
|
{
|
|
$newInvalidArgumentExceptionParams = @{
|
|
ArgumentName = $key
|
|
Message = $script:localizedData.ErrorParametersNotSupportedWithCredential
|
|
}
|
|
New-InvalidArgumentException @newInvalidArgumentExceptionParams
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
Start-ProcessAsLocalSystemUser -Path $Path -Arguments $Arguments -Credential $Credential
|
|
}
|
|
catch
|
|
{
|
|
throw (New-Object -TypeName 'System.Management.Automation.ErrorRecord' `
|
|
-ArgumentList @( $_.Exception, 'Win32Exception', 'OperationStopped', $null ))
|
|
}
|
|
}
|
|
# Credential not passed in or running from a LocalSystem
|
|
else
|
|
{
|
|
try
|
|
{
|
|
Start-Process @startProcessArguments
|
|
}
|
|
catch [System.Exception]
|
|
{
|
|
$errorMessage = ($script:localizedData.ErrorStarting -f $Path, $_.Exception.Message)
|
|
|
|
New-InvalidOperationException -Message $errorMessage
|
|
}
|
|
}
|
|
|
|
Write-Verbose -Message ($script:localizedData.ProcessesStarted -f $Path)
|
|
|
|
# Before returning from Set-TargetResource we have to ensure a subsequent Test-TargetResource is going to work
|
|
if (-not (Wait-ProcessCount -ProcessSettings $getProcessCimInstanceArguments -ProcessCount 1))
|
|
{
|
|
$message = $script:localizedData.ErrorStarting -f $Path,
|
|
$script:localizedData.FailureWaitingForProcessesToStart
|
|
|
|
New-InvalidOperationException -Message $message
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Write-Verbose -Message ($script:localizedData.ProcessAlreadyStarted -f $Path)
|
|
}
|
|
}
|
|
|
|
Write-Verbose -Message ($script:localizedData.SetTargetResourceEndMessage -f $Path)
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Tests if the Windows process with the specified executable path and arguments is in
|
|
the specified state.
|
|
|
|
.PARAMETER Path
|
|
The path to the process executable. If this is the file name of the executable
|
|
(not the fully qualified path), the DSC resource will search the environment Path variable
|
|
($env:Path) to find the executable file. If the value of this property is a fully qualified
|
|
path, DSC will use the given Path variable to find the file. If the path is not found it
|
|
will throw an error. Relative paths are not allowed.
|
|
|
|
.PARAMETER Arguments
|
|
The arguments to pass to the process as a single string.
|
|
|
|
.PARAMETER Credential
|
|
The credential of the user account the process should be running under.
|
|
|
|
.PARAMETER Ensure
|
|
Specifies whether or not the process should exist.
|
|
If the process should exist, set this property to Present.
|
|
If the process should not exist, set this property to Absent.
|
|
The default value is Present.
|
|
|
|
.PARAMETER StandardOutputPath
|
|
Not used in Test-TargetResource.
|
|
|
|
.PARAMETER StandardErrorPath
|
|
Not used in Test-TargetResource.
|
|
|
|
.PARAMETER StandardInputPath
|
|
Not used in Test-TargetResource.
|
|
|
|
.PARAMETER WorkingDirectory
|
|
Not used in Test-TargetResource.
|
|
#>
|
|
function Test-TargetResource
|
|
{
|
|
[OutputType([System.Boolean])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$Path,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[AllowEmptyString()]
|
|
[System.String]
|
|
$Arguments,
|
|
|
|
[Parameter()]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Management.Automation.PSCredential]
|
|
[System.Management.Automation.Credential()]
|
|
$Credential,
|
|
|
|
[Parameter()]
|
|
[ValidateSet('Present', 'Absent')]
|
|
[System.String]
|
|
$Ensure = 'Present',
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$StandardOutputPath,
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$StandardErrorPath,
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$StandardInputPath,
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$WorkingDirectory
|
|
)
|
|
|
|
Write-Verbose -Message ($script:localizedData.TestTargetResourceStartMessage -f $Path)
|
|
|
|
Assert-PsDscContextNotRunAsUser
|
|
|
|
$Path = Expand-Path -Path $Path
|
|
|
|
$getProcessCimInstanceArguments = @{
|
|
Path = $Path
|
|
Arguments = $Arguments
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('Credential'))
|
|
{
|
|
$getProcessCimInstanceArguments['Credential'] = $Credential
|
|
}
|
|
|
|
$processCimInstances = @( Get-ProcessCimInstance @getProcessCimInstanceArguments )
|
|
|
|
Write-Verbose -Message ($script:localizedData.TestTargetResourceEndMessage -f $Path)
|
|
|
|
if ($Ensure -eq 'Absent')
|
|
{
|
|
return ($processCimInstances.Count -eq 0)
|
|
}
|
|
else
|
|
{
|
|
return ($processCimInstances.Count -gt 0)
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Expands a relative leaf path into a full, rooted path. Throws an invalid argument exception
|
|
if the path is not valid.
|
|
|
|
.PARAMETER Path
|
|
The relative leaf path to expand.
|
|
#>
|
|
function Expand-Path
|
|
{
|
|
[OutputType([System.String])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$Path
|
|
)
|
|
|
|
$Path = [Environment]::ExpandEnvironmentVariables($Path)
|
|
|
|
# Check to see if the path is rooted. If so, return it as is.
|
|
if ([IO.Path]::IsPathRooted($Path))
|
|
{
|
|
if (-not (Test-Path -Path $Path -PathType 'Leaf'))
|
|
{
|
|
New-InvalidArgumentException -ArgumentName 'Path' -Message ($script:localizedData.FileNotFound -f $Path)
|
|
}
|
|
|
|
return $Path
|
|
}
|
|
|
|
# Check to see if the path to the file exists in the current location. If so, return the full rooted path.
|
|
$rootedPath = [System.IO.Path]::GetFullPath($Path)
|
|
|
|
if ([System.IO.File]::Exists($rootedPath))
|
|
{
|
|
return $rootedPath
|
|
}
|
|
|
|
# If the path is not found, throw an exception
|
|
New-InvalidArgumentException -ArgumentName 'Path' -Message ($script:localizedData.FileNotFound -f $Path)
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves any process CIM instance objects that match the given path, arguments, and credential.
|
|
|
|
.PARAMETER Path
|
|
The executable path of the process to retrieve.
|
|
|
|
.PARAMETER Arguments
|
|
The arguments of the process to retrieve as a single string.
|
|
|
|
.PARAMETER Credential
|
|
The credential of the user account of the process to retrieve
|
|
|
|
.PARAMETER UseGetCimInstanceThreshold
|
|
If the number of processes returned by the Get-Process method is greater than or equal to
|
|
this value, this function will retrieve all processes at the executable path. This will
|
|
help the function execute faster. Otherwise, this function will retrieve each process
|
|
CIM instance with the process IDs retrieved from Get-Process.
|
|
#>
|
|
function Get-ProcessCimInstance
|
|
{
|
|
[OutputType([Microsoft.Management.Infrastructure.CimInstance[]])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$Path,
|
|
|
|
[Parameter()]
|
|
[System.String]
|
|
$Arguments,
|
|
|
|
[Parameter()]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Management.Automation.PSCredential]
|
|
[System.Management.Automation.Credential()]
|
|
$Credential,
|
|
|
|
[Parameter()]
|
|
[ValidateRange(0, [System.Int32]::MaxValue)]
|
|
[System.Int32]
|
|
$UseGetCimInstanceThreshold = 8
|
|
)
|
|
|
|
$processName = [IO.Path]::GetFileNameWithoutExtension($Path)
|
|
|
|
$getProcessResult = @( Get-Process -Name $processName -ErrorAction 'SilentlyContinue' )
|
|
|
|
$processCimInstances = @()
|
|
|
|
if ($getProcessResult.Count -ge $UseGetCimInstanceThreshold)
|
|
{
|
|
$escapedPathForWqlFilter = ConvertTo-EscapedStringForWqlFilter -FilterString $Path
|
|
$wqlFilter = "ExecutablePath = '$escapedPathForWqlFilter'"
|
|
|
|
$processCimInstances = Get-CimInstance -ClassName 'Win32_Process' -Filter $wqlFilter
|
|
}
|
|
else
|
|
{
|
|
foreach ($process in $getProcessResult)
|
|
{
|
|
if ($process.Path -ieq $Path)
|
|
{
|
|
Write-Verbose -Message ($script:localizedData.VerboseInProcessHandle -f $process.Id)
|
|
$getCimInstanceParams = @{
|
|
ClassName = 'Win32_Process'
|
|
Filter = "ProcessId = $($process.Id)"
|
|
ErrorAction = 'SilentlyContinue'
|
|
}
|
|
$processCimInstances += Get-CimInstance @getCimInstanceParams
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($PSBoundParameters.ContainsKey('Credential'))
|
|
{
|
|
$splitCredentialResult = Split-Credential -Credential $Credential
|
|
$domain = $splitCredentialResult.Domain
|
|
$userName = $splitCredentialResult.UserName
|
|
$processesWithCredential = @()
|
|
|
|
foreach ($process in $processCimInstances)
|
|
{
|
|
if ((Get-ProcessOwner -Process $process) -eq "$domain\$userName")
|
|
{
|
|
$processesWithCredential += $process
|
|
}
|
|
}
|
|
$processCimInstances = $processesWithCredential
|
|
}
|
|
|
|
if ($null -eq $Arguments)
|
|
{
|
|
$Arguments = [System.String]::Empty
|
|
}
|
|
|
|
$processesWithMatchingArguments = @()
|
|
|
|
foreach ($process in $processCimInstances)
|
|
{
|
|
if ((Get-ArgumentsFromCommandLineInput -CommandLineInput $process.CommandLine) -eq $Arguments)
|
|
{
|
|
$processesWithMatchingArguments += $process
|
|
}
|
|
}
|
|
|
|
return $processesWithMatchingArguments
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Converts a string to an escaped string to be used in a WQL filter such as the one passed in
|
|
the Filter parameter of Get-WmiObject.
|
|
|
|
.PARAMETER FilterString
|
|
The string to convert.
|
|
#>
|
|
function ConvertTo-EscapedStringForWqlFilter
|
|
{
|
|
[OutputType([System.String])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$FilterString
|
|
)
|
|
|
|
return $FilterString.Replace("\", "\\").Replace('"', '\"').Replace("'", "\'")
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the owner of a Process.
|
|
|
|
.PARAMETER Process
|
|
The Process to retrieve the owner of.
|
|
|
|
.NOTES
|
|
If the process was killed by the time this function is called, this function will throw a
|
|
WMIMethodException with the message "Not found".
|
|
#>
|
|
function Get-ProcessOwner
|
|
{
|
|
[OutputType([System.String])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNull()]
|
|
[System.Object]
|
|
$Process
|
|
)
|
|
|
|
$owner = Get-ProcessOwnerCimInstance -Process $Process -ErrorAction 'SilentlyContinue'
|
|
|
|
if ($null -ne $owner)
|
|
{
|
|
if ($null -ne $owner.Domain)
|
|
{
|
|
return ($owner.Domain + '\' + $owner.User)
|
|
}
|
|
else
|
|
{
|
|
# return the default domain
|
|
return ($env:computerName + '\' + $owner.User)
|
|
}
|
|
}
|
|
|
|
return [System.String]::Empty
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Wrapper function to retrieve the CIM instance of the owner of a process
|
|
|
|
.PARAMETER Process
|
|
The process to retrieve the CIM instance of the owner of.
|
|
|
|
.NOTES
|
|
If the process was killed by the time this function is called, this function will throw a
|
|
WMIMethodException with the message "Not found".
|
|
#>
|
|
function Get-ProcessOwnerCimInstance
|
|
{
|
|
[OutputType([Microsoft.Management.Infrastructure.CimInstance])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNull()]
|
|
[System.Object]
|
|
$Process
|
|
)
|
|
|
|
return Invoke-CimMethod -InputObject $Process -MethodName 'GetOwner' -ErrorAction 'SilentlyContinue'
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Retrieves the 'arguments' part of command line input.
|
|
|
|
.PARAMETER CommandLineInput
|
|
The command line input to retrieve the arguments from.
|
|
|
|
.EXAMPLE
|
|
Get-ArgumentsFromCommandLineInput -CommandLineInput 'C:\temp\a.exe X Y Z'
|
|
Returns 'X Y Z'.
|
|
#>
|
|
function Get-ArgumentsFromCommandLineInput
|
|
{
|
|
[OutputType([System.String])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter()]
|
|
[System.String]
|
|
$CommandLineInput
|
|
)
|
|
|
|
if ([System.String]::IsNullOrWhitespace($CommandLineInput))
|
|
{
|
|
return [System.String]::Empty
|
|
}
|
|
|
|
$CommandLineInput = $CommandLineInput.Trim()
|
|
|
|
if ($CommandLineInput.StartsWith('"'))
|
|
{
|
|
$endOfCommandChar = [System.Char] '"'
|
|
}
|
|
else
|
|
{
|
|
$endOfCommandChar = [System.Char] ' '
|
|
}
|
|
|
|
$endofCommandIndex = $CommandLineInput.IndexOf($endOfCommandChar, 1)
|
|
|
|
if ($endofCommandIndex -eq -1)
|
|
{
|
|
return [System.String]::Empty
|
|
}
|
|
|
|
return $CommandLineInput.Substring($endofCommandIndex + 1).Trim()
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Throws an invalid argument exception if the given hashtable contains the given key(s).
|
|
|
|
.PARAMETER Hashtable
|
|
The hashtable to check the keys of.
|
|
|
|
.PARAMETER Key
|
|
The key(s) that should not be in the hashtable.
|
|
#>
|
|
function Assert-HashtableDoesNotContainKey
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[System.Collections.Hashtable]
|
|
$Hashtable,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[System.String[]]
|
|
$Key
|
|
)
|
|
|
|
foreach ($keyName in $Key)
|
|
{
|
|
if ($Hashtable.ContainsKey($keyName))
|
|
{
|
|
New-InvalidArgumentException -ArgumentName $keyName `
|
|
-Message ($script:localizedData.ParameterShouldNotBeSpecified -f $keyName)
|
|
}
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Waits for the given amount of time for the given number of processes with the given settings
|
|
to be running. If not all processes are running by 'WaitTime', the function returns
|
|
false, otherwise it returns true.
|
|
|
|
.PARAMETER ProcessSettings
|
|
The settings for the running process(es) that we're getting the count of.
|
|
|
|
.PARAMETER ProcessCount
|
|
The number of processes running to wait for.
|
|
|
|
.PARAMETER WaitTime
|
|
The amount of milliseconds to wait for all processes to be running.
|
|
Default is 2000.
|
|
#>
|
|
function Wait-ProcessCount
|
|
{
|
|
[OutputType([System.Boolean])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Collections.Hashtable]
|
|
$ProcessSettings,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateRange(0, [System.Int32]::MaxValue)]
|
|
[System.Int32]
|
|
$ProcessCount,
|
|
|
|
[Parameter()]
|
|
[System.Int32]
|
|
$WaitTime = 200000
|
|
)
|
|
|
|
$startTime = [System.DateTime]::Now
|
|
|
|
do
|
|
{
|
|
$actualProcessCount = @( Get-ProcessCimInstance @ProcessSettings ).Count
|
|
} while ($actualProcessCount -ne $ProcessCount -and ([System.DateTime]::Now - $startTime).TotalMilliseconds -lt $WaitTime)
|
|
|
|
return $actualProcessCount -eq $ProcessCount
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Throws an error if the given path argument is not rooted.
|
|
|
|
.PARAMETER PathArgumentName
|
|
The name of the path argument that should be rooted.
|
|
|
|
.PARAMETER PathArgument
|
|
The path arguments that should be rooted.
|
|
#>
|
|
function Assert-PathArgumentRooted
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$PathArgumentName,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$PathArgument
|
|
)
|
|
|
|
if (-not ([IO.Path]::IsPathRooted($PathArgument)))
|
|
{
|
|
$message = $script:localizedData.PathShouldBeAbsolute -f $PathArgumentName, $PathArgument
|
|
|
|
New-InvalidArgumentException -ArgumentName 'Path' `
|
|
-Message $message
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Throws an error if the given path argument does not exist.
|
|
|
|
.PARAMETER PathArgumentName
|
|
The name of the path argument that should exist.
|
|
|
|
.PARAMETER PathArgument
|
|
The path argument that should exist.
|
|
#>
|
|
function Assert-PathArgumentValid
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$PathArgumentName,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$PathArgument
|
|
)
|
|
|
|
if (-not (Test-Path -Path $PathArgument))
|
|
{
|
|
$message = $script:localizedData.PathShouldExist -f $PathArgument, $PathArgumentName
|
|
|
|
New-InvalidArgumentException -ArgumentName 'Path' `
|
|
-Message $message
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Tests if the current user is from the local system.
|
|
#>
|
|
function Test-IsRunFromLocalSystemUser
|
|
{
|
|
[OutputType([System.Boolean])]
|
|
[CmdletBinding()]
|
|
param ()
|
|
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = New-Object -TypeName Security.Principal.WindowsPrincipal -ArgumentList $identity
|
|
|
|
return $principal.Identity.IsSystem
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Starts the process with the given credential when the user is a local system user.
|
|
|
|
.PARAMETER Path
|
|
The path to the process executable.
|
|
|
|
.PARAMETER Arguments
|
|
Indicates a string of arguments to pass to the process as-is.
|
|
|
|
.PARAMETER Credential
|
|
Indicates the credential for starting the process.
|
|
#>
|
|
function Start-ProcessAsLocalSystemUser
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.String]
|
|
$Path,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[AllowEmptyString()]
|
|
[System.String]
|
|
$Arguments,
|
|
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Management.Automation.PSCredential]
|
|
[System.Management.Automation.Credential()]
|
|
$Credential
|
|
)
|
|
|
|
$splitCredentialResult = Split-Credential -Credential $Credential
|
|
|
|
<#
|
|
Internally we use win32 api LogonUser() with
|
|
dwLogonType == LOGON32_LOGON_NETWORK_CLEARTEXT.
|
|
|
|
It grants the process ability for second-hop.
|
|
#>
|
|
Import-DscNativeMethods
|
|
|
|
[PSDesiredStateConfiguration.NativeMethods]::CreateProcessAsUser( "$Path $Arguments", $splitCredentialResult.Domain,
|
|
$splitCredentialResult.UserName, $Credential.Password,
|
|
$false, [Ref]$null )
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Splits a credential into a username and domain without calling GetNetworkCredential.
|
|
Calls to GetNetworkCredential expose the password as plain text in memory.
|
|
|
|
.PARAMETER Credential
|
|
The credential to pull the username and domain out of.
|
|
|
|
.NOTES
|
|
Supported formats: DOMAIN\username, username@domain
|
|
#>
|
|
function Split-Credential
|
|
{
|
|
[OutputType([System.Collections.Hashtable])]
|
|
[CmdletBinding()]
|
|
param
|
|
(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[System.Management.Automation.PSCredential]
|
|
[System.Management.Automation.Credential()]
|
|
$Credential
|
|
)
|
|
|
|
$wrongFormat = $false
|
|
|
|
if ($Credential.UserName.Contains('\'))
|
|
{
|
|
$credentialSegments = $Credential.UserName.Split('\')
|
|
|
|
if ($credentialSegments.Length -gt 2)
|
|
{
|
|
# i.e. domain\user\foo
|
|
$wrongFormat = $true
|
|
}
|
|
else
|
|
{
|
|
$domain = $credentialSegments[0]
|
|
$userName = $credentialSegments[1]
|
|
}
|
|
}
|
|
elseif ($Credential.UserName.Contains('@'))
|
|
{
|
|
$credentialSegments = $Credential.UserName.Split('@')
|
|
|
|
if ($credentialSegments.Length -gt 2)
|
|
{
|
|
# i.e. user@domain@foo
|
|
$wrongFormat = $true
|
|
}
|
|
else
|
|
{
|
|
$UserName = $credentialSegments[0]
|
|
$Domain = $credentialSegments[1]
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Support for default domain (localhost)
|
|
$domain = $env:computerName
|
|
$userName = $Credential.UserName
|
|
}
|
|
|
|
if ($wrongFormat)
|
|
{
|
|
$message = $script:localizedData.ErrorInvalidUserName -f $Credential.UserName
|
|
|
|
New-InvalidArgumentException -ArgumentName 'Credential' -Message $message
|
|
}
|
|
|
|
return @{
|
|
Domain = $domain
|
|
UserName = $userName
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Asserts that the PsDscContext is not run as user.
|
|
Throws an invalid argument exception if DSC is running as a specific user
|
|
(the PsDscRunAsCredential parameter was provided to DSC).
|
|
|
|
.NOTES
|
|
Strict mode is turned off for this function since it does not recognize $PsDscContext
|
|
#>
|
|
function Assert-PsDscContextNotRunAsUser
|
|
{
|
|
[CmdletBinding()]
|
|
param
|
|
()
|
|
|
|
Set-StrictMode -Off
|
|
|
|
if ($null -ne $PsDscContext.RunAsUser)
|
|
{
|
|
$newInvalidArgumentExceptionParams = @{
|
|
ArgumentName = 'PsDscRunAsCredential'
|
|
Message = ($script:localizedData.ErrorRunAsCredentialParameterNotSupported -f $PsDscContext.RunAsUser)
|
|
}
|
|
|
|
New-InvalidArgumentException @newInvalidArgumentExceptionParams
|
|
}
|
|
}
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Imports the DSC native methods so that a process can be started with a credential
|
|
for a user from the local system.
|
|
Currently Start-Process, which is the command used otherwise, cannot do this.
|
|
#>
|
|
function Import-DscNativeMethods
|
|
{
|
|
$dscNativeMethodsSource = @"
|
|
|
|
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("$($script: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("$($script: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("$($script:localizedData.PrivilegeLookingUpError)" + Marshal.GetLastWin32Error().ToString());
|
|
}
|
|
bResult = AdjustTokenPrivileges(
|
|
htok,
|
|
false,
|
|
ref tp,
|
|
0,
|
|
IntPtr.Zero,
|
|
IntPtr.Zero
|
|
);
|
|
if (!bResult)
|
|
{
|
|
ThrowException("$($script: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("$($script: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("$($script:localizedData.CouldNotCreateProcessError)" + Marshal.GetLastWin32Error().ToString());
|
|
}
|
|
if (waitForExit) {
|
|
int status = WaitForSingleObject(pi.hProcess, -1);
|
|
if(status == -1)
|
|
{
|
|
ThrowException("$($script:localizedData.WaitFailedError)" + Marshal.GetLastWin32Error().ToString());
|
|
}
|
|
|
|
bResult = GetExitCodeProcess(pi.hProcess, out ExitCode);
|
|
if(!bResult)
|
|
{
|
|
ThrowException("$($script: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 not on Nano:
|
|
Add-Type -TypeDefinition $dscNativeMethodsSource -ReferencedAssemblies 'System.ServiceProcess'
|
|
}
|