Skip to content

Instantly share code, notes, and snippets.

@amnich
Last active January 8, 2018 23:24
Show Gist options
  • Save amnich/6485c57e15d8272205d9c11dbaf419b7 to your computer and use it in GitHub Desktop.
Save amnich/6485c57e15d8272205d9c11dbaf419b7 to your computer and use it in GitHub Desktop.
function Invoke-CommandAs {
<#
.SYNOPSIS
Invoke Command using ScheduledJob with Credential on remote computer.
.DESCRIPTION
Invoke a ScheduledJob on machine (Remote if ComputerName/Session is provided).
ScheduledJob will be executed with current user credentials if no -As credential are provided.
Because the ScheduledJob is executed by the Task Scheduler, it runs as if it was ran locally. And not from within the Powershell Session.
Resolving the Double Hop limitations by Powershell Remote Sessions.
By Marc R Kellerman (@mkellerman)
.PARAMETER ComputerName
Specifies the computers on which the command runs. The default is the local computer.
When you use the ComputerName parameter, Windows PowerShell creates a temporary connection that is used only to run the specified command and is then closed. If you need a persistent connection, use the Session parameter.
Type the NETBIOS name, IP address, or fully qualified domain name of one or more computers in a comma-separated list. To specify the local computer, type the computer name, localhost, or a dot (.).
To use an IP address in the value of ComputerName , the command must include the Credential parameter. Also, the computer must be configured for HTTPS transport or the IP address of the remote computer must be included in the WinRM TrustedHosts
list on the local computer. For instructions for adding a computer name to the TrustedHosts list, see "How to Add a Computer to the Trusted Host List" in about_Remote_Troubleshooting.
On Windows Vista and later versions of the Windows operating system, to include the local computer in the value of ComputerName , you must open Windows PowerShell by using the Run as administrator option.
.PARAMETER Credential
Specifies a user account that has permission to perform this action. The default is the current user.
Type a user name, such as User01 or Domain01\User01. Or, enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, this cmdlet prompts you for a password.
.PARAMETER Session
Specifies an array of sessions in which this cmdlet runs the command. Enter a variable that contains PSSession objects or a command that creates or gets the PSSession objects, such as a New-PSSession or Get-PSSession command.
When you create a PSSession , Windows PowerShell establishes a persistent connection to the remote computer. Use a PSSession to run a series of related commands that share data. To run a single command or a series of unrelated commands, use the
ComputerName parameter. For more information, see about_PSSessions.
.PARAMETER ScriptBlock
Specifies the commands to run. Enclose the commands in braces ( { } ) to create a script block. This parameter is required.
By default, any variables in the command are evaluated on the remote computer. To include local variables in the command, use ArgumentList .
.PARAMETER ArgumentList
Supplies the values of local variables in the command. The variables in the command are replaced by these values before the command is run on the remote computer. Enter the values in a comma-separated list. Values are associated with variables in
the order that they are listed. The alias for ArgumentList is Args.
The values in the ArgumentList parameter can be actual values, such as 1024, or they can be references to local variables, such as $max.
To use local variables in a command, use the following command format:
`{param($<name1>[, $<name2>]...) <command-with-local-variables>} -ArgumentList <value> -or- <local-variable>`
The param keyword lists the local variables that are used in the command. ArgumentList supplies the values of the variables, in the order that they are listed.
.PARAMETER As
SchduledJob will be executed using this user. Specifies a user account that has permission to perform this action. The default is the current user.
Type a user name, such as User01 or Domain01\User01. Or, enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a user name, this cmdlet prompts you for a password.
.PARAMETER AsJob
Indicates that this cmdlet runs the command as a background job on a remote computer. Use this parameter to run commands that take an extensive time to finish.
When you use the AsJob parameter, the command returns an object that represents the job, and then displays the command prompt. You can continue to work in the session while the job finishes. To manage the job, use the Job cmdlets. To get the job
results, use the Receive-Job cmdlet.
The AsJob parameter resembles using the Invoke-Command cmdlet to run a Start-Job command remotely. However, with AsJob , the job is created on the local computer, even though the job runs on a remote computer, and the results of the remote job are
automatically returned to the local computer.
For more information about Windows PowerShell background jobs, see about_Jobs (http://go.microsoft.com/fwlink/?LinkID=113251) and about_Remote_Jobs (http://go.microsoft.com/fwlink/?LinkID=135184).
.PARAMETER JobName
Specifies a friendly name for the background job. By default, jobs are named Job<n>, where <n> is an ordinal number.
If you use the JobName parameter in a command, the command is run as a job, and Invoke-Command returns a job object, even if you do not include AsJob in the command.
For more information about Windows PowerShell background jobs, see about_Jobs (http://go.microsoft.com/fwlink/?LinkID=113251).
.PARAMETER ThrottleLimit
Specifies the maximum number of concurrent connections that can be established to run this command. If you omit this parameter or enter a value of 0, the default value, 32, is used.
The throttle limit applies only to the current command, not to the session or to the computer.
#>
[cmdletbinding(DefaultParameterSetName="None")]
Param(
[Parameter(Mandatory = $false, ParameterSetName = 'ComputerName', Position=0)]
[string[]]$ComputerName,
[Parameter(Mandatory = $false, ParameterSetName = 'ComputerName')]
[System.Management.Automation.PSCredential]$Credential,
[Parameter(Mandatory = $false, ParameterSetName = 'PSSession', Position=0)]
[System.Management.Automation.Runspaces.PSSession[]]$Session,
[Parameter(Mandatory = $true, Position=1)]
[ScriptBlock]$ScriptBlock,
[Parameter(Mandatory = $false)]
[Object[]]$ArgumentList,
[Parameter(Mandatory = $false, ParameterSetName = 'ComputerName')]
[Parameter(Mandatory = $false, ParameterSetName = 'PSSession')]
[System.Management.Automation.PSCredential]$As,
[Parameter(Mandatory = $false, ParameterSetName = 'ComputerName')]
[Parameter(Mandatory = $false, ParameterSetName = 'PSSession')]
[Switch]$AsJob,
[Parameter(Mandatory = $false, ParameterSetName = 'ComputerName')]
[Parameter(Mandatory = $false, ParameterSetName = 'PSSession')]
[String]$JobName,
[Parameter(Mandatory = $false, ParameterSetName = 'ComputerName')]
[Parameter(Mandatory = $false, ParameterSetName = 'PSSession')]
[Int]$ThrottleLimit
)
Function Get-FunctionsToExport {
param($ScriptBlock)
function Find-CommandInAst {
param([System.Management.Automation.Language.Ast] $Ast)
end {
return $Ast -is [System.Management.Automation.Language.CommandAst]
}
}
function Find-FunctionCommands {
param ($functionName)
$ast = (dir function:\ | where Name -eq $functionName).ScriptBlock.Ast
try {$ast.FindAll(${function:Find-CommandInAst}, $true)}
catch{}
}
$toProcess = @{}
try {
($ScriptBlock.ast.FindAll(${function:Find-CommandInAst}, $true)) | ForEach-Object {
$toProcess.Add($_.CommandElements[0],1)
}
}
catch {break}
while (($toProcess.Values | Measure-Object -sum).sum -gt 0){
$toadd = @{}
foreach ($single in $toProcess.GetEnumerator() | where Value -gt 0){
$commandName = $single.name
$toadd[$commandName] = 0
$commands = Find-FunctionCommands -functionName $single.name
if ($commands){
foreach ($command in $commands | % {$_.CommandElements[0]} | select -Unique){
if ((dir function:\ | where Name -eq $command) -and ($toProcess[$command] -eq $null)){
try {$toAdd.add($command,1)}
catch{}
}
}
}
}
foreach ($one in $toAdd.GetEnumerator()){
$toProcess[$one.name] = $one.Value
}
}
foreach ($functionToExport in $toProcess.GetEnumerator()){
(dir function:\ | where Name -eq $functionToExport.name).ScriptBlock.Ast.Extent.text
}
}
function Invoke-ScheduledJob {
[cmdletbinding()]
Param(
[Parameter(Mandatory = $true)][ScriptBlock]$ScriptBlock,
[Parameter(Mandatory = $false)][Object[]]$ArgumentList,
[Parameter(Mandatory = $false)][PSCredential]$Credential
)
Begin { $ScheduledJobName = [guid]::NewGuid().Guid }
Process {
Try {
Write-Debug "Registering ScheduledJob: $ScheduledJobName"
$JobParameters = @{ Name = $ScheduledJobName }
If ($ScriptBlock) { $JobParameters['ScriptBlock'] = $ScriptBlock }
If ($ArgumentList) { $JobParameters['ArgumentList'] = $ArgumentList }
If ($Credential) { $JobParameters['Credential'] = $Credential }
$ScheduledJob = Register-ScheduledJob -RunNow @JobParameters -ErrorAction Stop
Write-Debug "Get ScheduledJob"
While (-Not($Job = Get-Job -Name $ScheduledJob.Name -ErrorAction SilentlyContinue)) { Sleep -m 100 }
Write-Debug "Receive ScheduledJob"
$Job | Wait-Job | Receive-Job -Wait -AutoRemoveJob
} Catch { Throw $_ }
}
End {
If ($ScheduledJob) {
Write-Debug "Remove ScheduledJob"
Try { $ScheduledJob | Unregister-ScheduledJob -Force -Confirm:$False | Out-Null } Catch {}
}
}
}
$_HelperFunctions = Get-FunctionsToExport -ScriptBlock $ScriptBlock
If ($ScriptBlock){
$newScriptBlock = ""
foreach ($helperFunction in $_HelperFunctions){
$newScriptBlock += $helperFunction + "`n"
}
$newScriptBlock += $ScriptBlock
$ScriptBlock = [ScriptBlock]::Create($newScriptBlock)
}
If ($ComputerName -or $Session) {
# Collection the functions to bring with us in the remote session:
$_Function = ${Function:Invoke-ScheduledJob}.Ast.Extent.Text
$Parameters = @{}
If ($ComputerName) { $Parameters['ComputerName'] = $ComputerName }
If ($Credential) { $Parameters['Credential'] = $Credential }
If ($Session) { $Parameters['Session'] = $Session }
If ($AsJob) { $Parameters['AsJob'] = $AsJob }
If ($JobName) { $Parameters['JobName'] = $JobName }
If ($ThrottleLimit) { $Parameters['ThrottleLimit'] = $ThrottleLimit }
Invoke-Command @Parameters -ScriptBlock {
# Create the functions we packed up with us previously:
$Using:_Function | % { Invoke-Expression $_ }
$Parameters = @{}
If ($Using:ScriptBlock) {
$Parameters['ScriptBlock'] = [ScriptBlock]::Create($Using:scriptBlock)
}
If ($Using:ArgumentList) { $Parameters['ArgumentList'] = $Using:ArgumentList }
If ($Using:As) { $Parameters['Credential'] = $Using:As }
Invoke-ScheduledJob @Parameters
}
} Else {
$Parameters = @{}
If ($ScriptBlock) {
$Parameters['ScriptBlock'] = $ScriptBlock
}
If ($ArgumentList) { $Parameters['ArgumentList'] = $ArgumentList }
If ($As) { $Parameters['Credential'] = $As }
Invoke-ScheduledJob @Parameters
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment