Skip to content

Instantly share code, notes, and snippets.

@scriptingstudio
Last active March 29, 2025 10:35
Show Gist options
  • Save scriptingstudio/1796ba348710983d985e2836f47b9314 to your computer and use it in GitHub Desktop.
Save scriptingstudio/1796ba348710983d985e2836f47b9314 to your computer and use it in GitHub Desktop.
Yet another way to run timed Powershell commands and scripts
function Invoke-PSCommand {
[CmdletBinding(DefaultParameterSetName='scriptblock')]
[alias('ipsc')]
param (
[Parameter(Mandatory,Position=0,ParameterSetName='scriptblock')]
[ValidateNotNullOrEmpty()]
[alias('sb')][ScriptBlock] $ScriptBlock,
[Parameter(Mandatory,Position=0,ParameterSetName='file')]
[ValidateNotNullOrEmpty()]
[alias('fn')][string] $FileName,
[Parameter(Position=1)]
[alias('timeout','to')][int] $OperationTimeoutSec,
[string[]] $Variables,
[string[]] $Functions,
[string[]] $Switches,
[hashtable] $Parameters = @{},
[switch] $Core, # experimental
[switch] $Quiet
)
if ($Quiet) {
$ErrorActionPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
}
# Adjust defaults
if ($FileName) {
$ScriptBlock = [ScriptBlock]::Create((Get-Content $FileName -Raw -ErrorAction $ErrorActionPreference))
if ($Quiet -and -not $ScriptBlock) {return}
}
if ($ScriptBlock.ToString().Length -eq 0) {return} # nothing to do
if ($Parameters -eq $null) {$Parameters = @{}}
# "using:" variables
$usingParams = @{}
$ast = $ScriptBlock.Ast.FindAll({$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]}, $true)
foreach ($usingstatement in $ast) {
$varText = $usingstatement.Extent.Text
$varPath = $usingstatement.SubExpression.VariablePath.UserPath
$key = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($varText.ToLower()))
if (-not $usingParams.ContainsKey($key)) {
$usingParams.Add($key, $PSCmdlet.SessionState.PSVariable.GetValue($varPath))
}
}
if ($usingParams.Count) {
$Parameters['--%'] = $usingParams
}
# Init session
$iss = if ($core) {[InitialSessionState]::CreateDefault2()} else {[InitialSessionState]::CreateDefault()}
# import variables
foreach ($v in $Variables) {
if (-not $v) {continue}
$iss.Variables.Add([System.Management.Automation.Runspaces.SessionStateVariableEntry]::new($v, (Get-Variable $v -ErrorAction 0).value, $null))
}
# import functions
foreach ($f in $Functions) {
if (-not $f) {continue}
$def = (Get-Command $f -ErrorAction 0).Definition
if ($def) {
$iss.Commands.Add([System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new($f, $def))
}
}
$Runspace = [runspacefactory]::CreateRunspace($iss)
$PowerShell = [powershell]::Create()
$null = $PowerShell.AddScript($ScriptBlock)
if ($Parameters.Count) {$null = $PowerShell.AddParameters($Parameters)}
$PowerShell.Runspace = $Runspace
# Run session
$Runspace.Open()
if ($OperationTimeoutSec -gt 0) {
$IAsyncResult = $PowerShell.BeginInvoke() # start job
$timeoutms = $OperationTimeoutSec * 1000 - 150
$timer = [System.Diagnostics.Stopwatch]::StartNew() # job timer
do {[System.Threading.Thread]::Sleep(150)} # polling interval
until ($IAsyncResult.IsCompleted -or $timeoutms -lt $timer.Elapsed.TotalMilliseconds)
if ($IAsyncResult.IsCompleted) { # success: just in time
$PowerShell.EndInvoke($IAsyncResult) # get results
} elseif (-not $Quiet) {
Write-Warning "Run out of time" # Session timer has expired
}
$timer.Stop()
} else {
$PowerShell.Invoke() # get results
}
# show errors
if (-not $Quiet -and $PowerShell.HadErrors) {
foreach ($e in $PowerShell.Streams.Error) {
$PSCmdlet.WriteError($e)
}
}
# Cleanup session
$PowerShell.Stop()
$Runspace.Close()
$Runspace.Dispose()
$PowerShell.Dispose()
} # END Invoke-PSCommand
$timeout = 2
$pname = 'dllhost'
Invoke-PSCommand {Start-Sleep -milliSeconds 2200; Get-Process -Name $using:pname; fakecmd} $timeout
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment