Skip to content

Instantly share code, notes, and snippets.

@mhudasch
Created October 13, 2016 15:57
Show Gist options
  • Save mhudasch/9009c968db4decd6953a2a81a8dd8159 to your computer and use it in GitHub Desktop.
Save mhudasch/9009c968db4decd6953a2a81a8dd8159 to your computer and use it in GitHub Desktop.
Schedule an auto-start command to a remote machine with given rights and integrated cleaup after it ran leaving possible piped results.
#requires -version 5
Set-StrictMode -Version Latest
Function Add-StartupCommand {
[OutputType("ScheduledCommand")]
[CmdletBinding()]
param(
[Parameter(Mandatory=$true,Position=0)]
[String]$TaskName,
[Parameter(Mandatory=$true,Position=1)]
[ScriptBlock]$ScriptBlock,
[Parameter(Mandatory=$false,Position=2)]
[object[]]$ArgumentList,
[Parameter(Mandatory=$false,Position=3)]
[String[]]$ComputerName = $env:COMPUTERNAME,
[Parameter(Mandatory=$false,Position=4)]
[pscredential]$Credential,
[Parameter(Mandatory=$false,Position=5)]
[pscredential]$TaskCredential,
[Parameter(Mandatory=$false,Position=6)]
[Switch]$Elevated)
Process {
$block = [scriptblock] {
param($tn, $sc, $scargs, $cn, [pscredential]$cred, [pscredential]$tcred, $elev, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref)
$VerbosePreference = $vpref;
$ErrorActionPreference = $erpref;
$WarningPreference = $wpref;
$DebugPreference = $dpref;
$InformationPreference = $ipref;
[System.Management.Automation.PSSerializer]::Serialize($args) | Write-Verbose;
$registerBlock = [scriptblock] {
param($tn, $sc, $scargs, $elev, [pscredential]$tcred, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref)
$VBoxManageLogLocationPreference = $llpref;
$VBoxManageScriptLocationPreference = $scpref;
$VerbosePreference = $vpref;
$ErrorActionPreference = $erpref;
$WarningPreference = $wpref;
$DebugPreference = $dpref;
$InformationPreference = $ipref;
if(!(Get-Command -Name Write-LogFile -ErrorAction SilentlyContinue)) {
# remap the passed logging function script
New-Item -Path function: -Name 'Write-LogFile' -Value $logsc | Out-Null;
}
if(!(Test-Path $llpref)) { New-Item -ItemType Directory -Path $llpref -Force | Out-Null; }
if(!(Test-Path $scpref)) { New-Item -ItemType Directory -Path $scpref -Force | Out-Null; }
$path = Join-Path $scpref ($tn + ".cmd");
$scrPath = Join-Path $scpref ($tn + ".ps1");
$logPath = Join-Path $llpref ($tn + ".log");
$outPath = Join-Path $scpref($tn + ".out");
$errPath = Join-Path $scpref($tn + ".err");
$vrbPath = Join-Path $scpref($tn + ".vrb");
$wrnPath = Join-Path $scpref($tn + ".wrn");
$dbgPath = Join-Path $scpref($tn + ".dbg");
$infoPath = Join-Path $scpref($tn + ".info");
$result = @{
ScriptName=$tn;
ScriptCommandPath=$path;
ScriptPath=$scrPath;
ScriptLog=$logPath;
ComputerName=$env:ComputerName;
Script=$null;
TaskDefinition=$null;
OutPath=$outPath;
ErrorPath=$errPath;
WarningPath=$wrnPath;
VerbosePath=$vrbPath;
DebugPath=$dbgPath;
InformationPath=$infoPath;
};
$serializeObjectDepth = 10;
$TASK_LOGON_PASSWORD = 1;
("".PadRight(100,"=")) | Write-LogFile -Name $tn | Out-Null;
"[$env:ComputerName] Script-Name: '$tn'." | Write-LogFile -Name $tn | Write-Verbose;
"[$env:ComputerName] Script-Path: '$path'." | Write-LogFile -Name $tn | Write-Verbose;
"[$env:ComputerName] Script-Log: '$logPath'." | Write-LogFile -Name $tn | Write-Verbose;
"[$env:ComputerName] Script-Return: '$outPath'." | Write-LogFile -Name $tn | Write-Verbose;
#region wrapper script build
$scPreamble = "`$VerbosePreference = '$vpref';`r`n";
$scPreamble += "`$ErrorActionPreference = '$erpref';`r`n";
$scPreamble += "`$WarningPreference = '$wpref';`r`n";
$scPreamble += "`$DebugPreference = '$dpref';`r`n";
$scPreamble += "`$InformationPreference = '$ipref';`r`n";
$scPreamble += "`$script:ScheduledCommandName=`"$tn`";`r`n";
$scPreamble += "`$script:ScheduledCommandLogPath=`"$logPath`";`r`n";
$scPreamble += "`$script:PostponeAutoCleanup=`$false;`r`n";
$scPreamble += "`$scwarn=@(); `$scverbose=@(); `$scdebug=@(); `$scinfo=@();`r`n";
$scArgumentStatement = "-ArgumentList `$([System.Management.Automation.PSSerializer]::Deserialize(@`"`r`n$([System.Management.Automation.PSSerializer]::Serialize($scargs, 999))`r`n`"@))";
if($scargs) {
$scInvokation = "`r`n`(Invoke-Command -ScriptBlock { $sc } -ErrorAction Stop -ErrorVariable scerr $scArgumentStatement 3>&1 4>&1 5>&1 6>&1) | `r`n";
} else {
$scInvokation = "`r`n`(Invoke-Command -ScriptBlock { $sc } -ErrorAction Stop -ErrorVariable scerr 3>&1 4>&1 5>&1 6>&1) | `r`n";
}
$scStreamfilter = "ForEach-Object { `$t = `$_.GetType().Name; `$item = `$_; switch (`$t) { `"WarningRecord`" { `$scwarn+=`$item; } `"VerboseRecord`" { `$scverbose+=`$item; } `"DebugRecord`" { `$scdebug+=`$item; } `"InformationRecord`" { `$scinfo+=`$item; } default { `$item; } } } | ";
$scOutPipe = "Export-CliXml -Path `"$outPath`" -Depth $serializeObjectDepth -Force;`r`n";
$scErrPipe = "if(`$scerr) { `$scerr | Export-CliXml -Path `"$errPath`" -Depth $serializeObjectDepth -Force; }`r`n";
$scVrbPipe = "if(`$scverbose.Count -gt 0) { `$scverbose | Export-CliXml -Path `"$vrbPath`" -Depth $serializeObjectDepth -Force; }`r`n";
$scWrnPipe = "if(`$scwarn.Count -gt 0) { `$scwarn | Export-CliXml -Path `"$wrnPath`" -Depth $serializeObjectDepth -Force; }`r`n";
$scDbgPipe = "if(`$scdebug.Count -gt 0) { `$scdebug | Export-CliXml -Path `"$dbgPath`" -Depth $serializeObjectDepth -Force; }`r`n";
$scInfoPipe = "if(`$scinfo.Count -gt 0) { `$scinfo | Export-CliXml -Path `"$infoPath`" -Depth $serializeObjectDepth -Force; }`r`n";
$cleanup = "if(!`$script:PostponeAutoCleanup) { (& schtasks /Delete /F /TN $tn 2>&1) | Out-Null;`r`nRemove-Item -Path $path -Force | Out-Null;`r`nRemove-Item -Path $scrPath -Force | Out-Null; }";
$finalScript = "$scPreamble try { $scInvokation $scStreamfilter $scOutPipe $scErrPipe $scVrbPipe $scWrnPipe $scDbgPipe $scInfoPipe } catch [System.Exception] { `$(`$_.Exception | Format-List -Force | Out-String) | Out-File `"$logPath`" -Append -Encoding utf8; `$_.Exception | Export-CliXml -Path `"$errPath`" -Depth $serializeObjectDepth -Force; } $cleanup";
$result.Script = $finalScript;
#endregion
Set-Content -Path $scrPath -Value $finalScript -Encoding UTF8 -Force | Out-Null;
$cmd = "powershell.exe -NoLogo -NonInteractive -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File $scrPath";
Set-Content -Path $path -Value $cmd -Encoding Ascii -Force | Out-Null;
$taskTemplate = "<?xml version=`"1.0`" encoding=`"UTF-16`"?><Task version=`"1.2`" xmlns=`"http://schemas.microsoft.com/windows/2004/02/mit/task`"><RegistrationInfo><Author>_AUTHOR_</Author><Description>_TASKNAME_</Description></RegistrationInfo><Triggers><BootTrigger><Enabled>true</Enabled></BootTrigger></Triggers><Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries><StopIfGoingOnBatteries>true</StopIfGoingOnBatteries><AllowHardTerminate>true</AllowHardTerminate><StartWhenAvailable>true</StartWhenAvailable><RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable><IdleSettings><Duration>PT10M</Duration><WaitTimeout>PT1H</WaitTimeout><StopOnIdleEnd>true</StopOnIdleEnd><RestartOnIdle>false</RestartOnIdle></IdleSettings><AllowStartOnDemand>true</AllowStartOnDemand><Enabled>true</Enabled><Hidden>false</Hidden><RunOnlyIfIdle>false</RunOnlyIfIdle><WakeToRun>false</WakeToRun><ExecutionTimeLimit>PT72H</ExecutionTimeLimit><Priority>7</Priority></Settings><Actions Context=`"Author`"><Exec><Command>_COMMAND_</Command></Exec></Actions><Principals><Principal id=`"Author`"><UserId>_TASKUSER_</UserId><LogonType>Password</LogonType><RunLevel>_RUNLEVEL_</RunLevel></Principal></Principals></Task>";
$taskTemplate = $taskTemplate -replace "_AUTHOR_", $([Security.Principal.WindowsIdentity]::GetCurrent().Name);
$taskTemplate = $taskTemplate -replace "_TASKNAME_", $tn;
$taskTemplate = $taskTemplate -replace "_COMMAND_", $path;
if($null -ne $tcred) {
$taskCredUserName = $tcred.UserName;
$taskTemplate = $taskTemplate -replace "_TASKUSER_", $taskCredUserName;
} else {
$taskTemplate = $taskTemplate -replace "_TASKUSER_", "SYSTEM";
}
if($elev) {
$taskTemplate = $taskTemplate -replace "_RUNLEVEL_", "HighestAvailable";
} else {
$taskTemplate = $taskTemplate -replace "_RUNLEVEL_", "LeastPrivilege";
}
$result.TaskDefinition = $taskTemplate;
$scheduler = New-Object -ComObject Schedule.Service;
"[$env:ComputerName] Connect to scheduler and register task." | Write-LogFile -Name $tn | Write-Verbose;
for ($i=1; $i -le 3; $i++) {
Try {
$scheduler.Connect($env:COMPUTERNAME);
"[$env:ComputerName] OK" | Write-LogFile -Name $tn | Write-Verbose;
Break;
} Catch {
if($i -ge 3) {
"[$env:ComputerName] Can't connect to Schedule service." | Write-LogFile -Name $tn | Write-Error -ErrorAction Stop
} else {
Start-Sleep -Seconds 1;
}
}
}
$task = $scheduler.NewTask($null);
$task.XmlText = $taskTemplate;
$rootFolder = $scheduler.GetFolder("\");
if($tcred) {
$taskCredUserName = $tcred.UserName;
$taskCredPasswordClear = $tcred.GetNetworkCredential().Password;
$td = ($rootFolder.RegisterTaskDefinition($tn, $task, 6, $taskCredUserName, $taskCredPasswordClear, $TASK_LOGON_PASSWORD) | Out-String);
} else {
$td = ($rootFolder.RegisterTaskDefinition($tn, $task, 6, "SYSTEM", $Null, $TASK_LOGON_PASSWORD) | Out-String);
}
$td | Write-LogFile -Name $tn | Out-Null;
"[$env:ComputerName] Task $tn added to scheduler." | Write-LogFile -Name $tn | Write-Verbose;
$result = New-Object -TypeName psobject -Property $result;
$result.psobject.typenames.Insert(0, "ScheduledCommand");
$result | Write-Output;
};
if($env:COMPUTERNAME -eq $cn) { # locally
if($null -eq $cred) {
Invoke-Command -ScriptBlock $registerBlock -ArgumentList @($tn, $sc, $scargs, $elev, $tcred, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref) | Write-Output;
} else {
Invoke-Command -Credential $cred -ScriptBlock $registerBlock -ArgumentList @($tn, $sc, $scargs, $elev, $tcred, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref) | Write-Output;
}
} else { #remotely
$list = New-Object System.Collections.ArrayList;
$list.AddRange(@($cn)); $l = $false;
if($list.Contains($env:COMPUTERNAME)) { $l = $true; $list.Remove($env:COMPUTERNAME); }
if($l) {
if($null -eq $cred) {
Invoke-Command -ScriptBlock $registerBlock -HideComputerName -ArgumentList @($tn, $sc, $scargs, $elev, $tcred, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref) | ForEach-Object {
$_.psobject.typenames.Insert(0, "ScheduledCommand"); $_; } | Write-Output;
} else {
Invoke-Command -Credential $cred -ScriptBlock $registerBlock -HideComputerName -ArgumentList @($tn, $sc, $scargs, $elev, $tcred, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref) | ForEach-Object {
$_.psobject.typenames.Insert(0, "ScheduledCommand"); $_; } | Write-Output;
}
}
if($null -eq $cred) {
Invoke-Command -ComputerName ($list.ToArray()) -ScriptBlock $registerBlock -HideComputerName -ArgumentList @($tn, $sc, $scargs, $elev, $tcred, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref) | ForEach-Object {
$_.psobject.typenames.Insert(0, "ScheduledCommand"); $_; } | Write-Output;
} else {
Invoke-Command -ComputerName ($list.ToArray()) -Credential $cred -ScriptBlock $registerBlock -HideComputerName -ArgumentList @($tn, $sc, $scargs, $elev, $tcred, $logsc, $llpref, $scpref, $erpref, $vpref, $wpref, $dpref, $ipref) | ForEach-Object {
$_.psobject.typenames.Insert(0, "ScheduledCommand"); $_; } | Write-Output;
}
}
};
Invoke-Command -ScriptBlock $block -ArgumentList @($TaskName, $ScriptBlock, $ArgumentList, $ComputerName, $Credential, $TaskCredential, $Elevated, ${function:Write-LogFile}.ToString(), $VBoxManageLogLocationPreference, $VBoxManageScriptLocationPreference, $ErrorActionPreference, $VerbosePreference, $WarningPreference, $DebugPreference, $InformationPreference) | Write-Output;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment