-
Notifications
You must be signed in to change notification settings - Fork 51
/
Copy pathInvoke-AsSystem.ps1
206 lines (163 loc) · 8.07 KB
/
Invoke-AsSystem.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
function Invoke-AsSystem {
<#
.SYNOPSIS
Function for running specified code under SYSTEM account.
.DESCRIPTION
Function for running specified code under SYSTEM account.
Helper files and sched. tasks are automatically deleted.
.PARAMETER scriptBlock
Scriptblock that should be run under SYSTEM account.
.PARAMETER computerName
Name of computer, where to run this.
.PARAMETER returnTranscript
Add creating of transcript to specified scriptBlock and returns its output.
.PARAMETER cacheToDisk
Necessity for long scriptBlocks. Content will be saved to disk and run from there.
.PARAMETER argument
If you need to pass some variables to the scriptBlock.
Hashtable where keys will be names of variables and values will be, well values :)
Example:
[hashtable]$Argument = @{
name = "John"
cities = "Boston", "Prague"
hash = @{var1 = 'value1','value11'; var2 = @{ key ='value' }}
}
Will in beginning of the scriptBlock define variables:
$name = 'John'
$cities = 'Boston', 'Prague'
$hash = @{var1 = 'value1','value11'; var2 = @{ key ='value' }
! ONLY STRING, ARRAY and HASHTABLE variables are supported !
.PARAMETER runAs
Let you change if scriptBlock should be running under SYSTEM, LOCALSERVICE or NETWORKSERVICE account.
Default is SYSTEM.
.EXAMPLE
Invoke-AsSystem {New-Item $env:TEMP\abc}
On local computer will call given scriptblock under SYSTEM account.
.EXAMPLE
Invoke-AsSystem {New-Item "$env:TEMP\$name"} -computerName PC-01 -ReturnTranscript -Argument @{name = 'someFolder'} -Verbose
On computer PC-01 will call given scriptblock under SYSTEM account i.e. will create folder 'someFolder' in C:\Windows\Temp.
Transcript will be outputted in console too.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[scriptblock] $scriptBlock,
[string] $computerName,
[switch] $returnTranscript,
[hashtable] $argument,
[ValidateSet('SYSTEM', 'NETWORKSERVICE', 'LOCALSERVICE')]
[string] $runAs = "SYSTEM",
[switch] $CacheToDisk
)
(Get-Variable runAs).Attributes.Clear()
$runAs = "NT Authority\$runAs"
#region prepare Invoke-Command parameters
# export this function to remote session (so I am not dependant whether it exists there or not)
$allFunctionDefs = "function Create-VariableTextDefinition { ${function:Create-VariableTextDefinition} }"
$param = @{
argumentList = $scriptBlock, $runAs, $CacheToDisk, $allFunctionDefs, $VerbosePreference, $ReturnTranscript, $Argument
}
if ($computerName -and $computerName -notmatch "localhost|$env:COMPUTERNAME") {
$param.computerName = $computerName
} else {
if (! ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
throw "You don't have administrator rights"
}
}
#endregion prepare Invoke-Command parameters
Invoke-Command @param -ScriptBlock {
param ($scriptBlock, $runAs, $CacheToDisk, $allFunctionDefs, $VerbosePreference, $ReturnTranscript, $Argument)
foreach ($functionDef in $allFunctionDefs) {
. ([ScriptBlock]::Create($functionDef))
}
$TranscriptPath = "$ENV:TEMP\Invoke-AsSYSTEM_$(Get-Random).log"
if ($Argument -or $ReturnTranscript) {
# define passed variables
if ($Argument) {
# convert hash to variables text definition
$VariableTextDef = Create-VariableTextDefinition $Argument
}
if ($ReturnTranscript) {
# modify scriptBlock to contain creation of transcript
$TranscriptStart = "Start-Transcript $TranscriptPath"
$TranscriptEnd = 'Stop-Transcript'
}
$ScriptBlockContent = ($TranscriptStart + "`n`n" + $VariableTextDef + "`n`n" + $ScriptBlock.ToString() + "`n`n" + $TranscriptStop)
Write-Verbose "####### SCRIPTBLOCK TO RUN"
Write-Verbose $ScriptBlockContent
Write-Verbose "#######"
$scriptBlock = [Scriptblock]::Create($ScriptBlockContent)
}
if ($CacheToDisk) {
$ScriptGuid = New-Guid
$null = New-Item "$($ENV:TEMP)\$($ScriptGuid).ps1" -Value $ScriptBlock -Force
$pwshcommand = "-ExecutionPolicy Bypass -Window Hidden -noprofile -file `"$($ENV:TEMP)\$($ScriptGuid).ps1`""
} else {
$encodedcommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($ScriptBlock))
$pwshcommand = "-ExecutionPolicy Bypass -Window Hidden -noprofile -EncodedCommand $($encodedcommand)"
}
$OSLevel = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").CurrentVersion
if ($OSLevel -lt 6.2) { $MaxLength = 8190 } else { $MaxLength = 32767 }
if ($encodedcommand.length -gt $MaxLength -and $CacheToDisk -eq $false) {
throw "The encoded script is longer than the command line parameter limit. Please execute the script with the -CacheToDisk option."
}
try {
#region create&run sched. task
$A = New-ScheduledTaskAction -Execute "$($ENV:windir)\system32\WindowsPowerShell\v1.0\powershell.exe" -Argument $pwshcommand
if ($runAs -match "\$") {
# pod gMSA uctem
$P = New-ScheduledTaskPrincipal -UserId $runAs -LogonType Password
} else {
# pod systemovym uctem
$P = New-ScheduledTaskPrincipal -UserId $runAs -LogonType ServiceAccount
}
$S = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -DontStopOnIdleEnd
$taskName = "RunAsSystem_" + (Get-Random)
try {
$null = New-ScheduledTask -Action $A -Principal $P -Settings $S -ea Stop | Register-ScheduledTask -Force -TaskName $taskName -ea Stop
} catch {
if ($_ -match "No mapping between account names and security IDs was done") {
throw "Account $runAs doesn't exist or cannot be used on $env:COMPUTERNAME"
} else {
throw "Unable to create helper scheduled task. Error was:`n$_"
}
}
# run scheduled task
Start-Sleep -Milliseconds 200
Start-ScheduledTask $taskName
# wait for sched. task to end
Write-Verbose "waiting on sched. task end ..."
$i = 0
while (((Get-ScheduledTask $taskName -ErrorAction silentlyContinue).state -ne "Ready") -and $i -lt 500) {
++$i
Start-Sleep -Milliseconds 200
}
# get sched. task result code
$result = (Get-ScheduledTaskInfo $taskName).LastTaskResult
# read & delete transcript
if ($ReturnTranscript) {
# return just interesting part of transcript
if (Test-Path $TranscriptPath) {
$transcriptContent = (Get-Content $TranscriptPath -Raw) -Split [regex]::escape('**********************')
# return command output
($transcriptContent[2] -split "`n" | Select-Object -Skip 2 | Select-Object -SkipLast 3) -join "`n"
Remove-Item $TranscriptPath -Force
} else {
Write-Warning "There is no transcript, command probably failed!"
}
}
if ($CacheToDisk) { $null = Remove-Item "$($ENV:TEMP)\$($ScriptGuid).ps1" -Force }
try {
Unregister-ScheduledTask $taskName -Confirm:$false -ea Stop
} catch {
throw "Unable to unregister sched. task $taskName. Please remove it manually"
}
if ($result -ne 0) {
throw "Command wasn't successfully ended ($result)"
}
#endregion create&run sched. task
} catch {
throw $_.Exception
}
}
}