-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathRestart-App.ps1
282 lines (224 loc) · 8.59 KB
/
Restart-App.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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
<#
I tried! I really tried. Sigh...
This mostly works but if your current user is a member of a group with elevated privileges
then the Command always starts elevated. Appears it's not possible to start a non-elevated
process from a privileged account on Windows!
.SYNOPSIS
Restart the named process. This can be used to restart applications such as Outlook on a nightly
basis. Apps such as this tend to have memory leaks or become unstable over time when dealing with
huge amounts of data on a very active system.
.PARAMETER Arguments
A string specifying the command line arguments to pass to Command on startup.
.PARAMETER Command
The command to start the application after the specified delay.
If not provided then try to use the command line of the named process.
This parameter is required when using the -Register switch.
.PARAMETER Delay
The amount of time to wait after stopping the application and until restarting
the application, specified as a TimeSpan "hh:mm:ss". Outlook will lock local pst data files,
preventing OneDrive from syncing them; this give OneDrive time to sync those files
.PARAMETER Name
The name of the process to restart.
.PARAMETER Password
A SecureString specifying the password for a named user. Must be specified with -User.
.PARAMETER User
The named user under which the scheduled task should run. Must be specified with -Password
.PARAMETER Register
Register a scheduled task to invoke the given command at a specified time.
.PARAMETER StartTime
The time of day to start the action. This can be any form accepted by the New-ScheduledTaskTrigger
command, so something like '2am', which is the default.
.PARAMETER GetCommand
If specified then report the command line of the specified running process. This value can be
used to specify the Command parameter when registering.
.EXAMPLE
❯ restart-app -Name outlook -GetCommand
... found process outlook, ID 3972, running "C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE"
To run the task in an elevated context:
> Restart-App -Name outlook -Register `
-Command 'C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE' `
-StartTime '2am' -Delay '02:00:00'
To run the task as a specific user: important when restarting Outlook as it must run as
the current user, otherwise it will run as admin and you can't click toast notification
> $password = ConvertTo-SecureString -AsPlainText <plainTextPassword>
> Restart-App -Name outlook -Register -command 'C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE' `
-User $env:username -Password $password -StartTime '2am' -Delay '02:00:00'
For quick testing, use: -Delay '00:00:05' -StartTime (get-date).addseconds(5)
#>
# CmdletBinding adds -Verbose functionality, SupportsShouldProcess adds -WhatIf
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $true)] [string] $Name,
[string] $Command,
[string] $Arguments,
[string] $Delay = '02:00:00',
[string] $StartTime = '2am',
[string] $User,
[System.Security.SecureString] $Password,
[switch] $Register,
[switch] $Unregister,
[switch] $GetCommand
)
Begin
{
function GetCommandLine
{
$process = (Get-Process $Name -ErrorAction:SilentlyContinue)
if ($process -eq $null)
{
$script:cmd = $null
Write-Host "... process $Name not found"
}
else
{
# get the commandline from the process, strip off quotes
$cmd = (Get-CimInstance win32_process -filter ("ProcessID={0}" -f $process.id)).CommandLine
Write-Host "... found process $Name, ID $($process.ID), running $cmd"
}
}
function Shutdown
{
$process = (Get-Process $Name -ErrorAction:SilentlyContinue)
if ($process -ne $null)
{
try
{
# get the commandline from the process, strip off quotes
$script:cmdline = (Get-CimInstance win32_process `
-filter ("ProcessID={0}" -f $process.id)).CommandLine.Replace('"', '')
Log "... terminating process $Name running $cmdline"
# terminating instead of graceful shutdown because can't connect using this:
# GetActiveObject undefined in pwsh Core. Any other way to connect?
#$outlook = [Runtime.Interopservices.Marshal]::GetActiveObject('Outlook.Application')
#$outlook.Quit()
$process.Kill()
$process = $null
Log "sleeping $($delay.ToString())"
Start-Sleep -Duration $delay
}
catch
{
Log "*** error stopping $Name"
Log "*** $($_)"
}
}
else
{
Log "... $Name process not found"
}
}
function Startup
{
Log "... starting $Name"
$credfile = "C:\Users\$User\$Name`.xml"
if (Test-Path $credfile)
{
try
{
$credential = Import-Clixml $credfile
# TODO: Remove this line:
#Remove-Item -Path $credfile -Force
Log "... running as $($credential.Username) with provided credentials"
if ($Arguments)
{
Log "... starting -Command `"$Command`" -Arguments `"$Arguments`""
Invoke-Command -Credential $credential -ComputerName $env:ComputerName `
-ScriptBlock { & $Command $Arguments }
}
else
{
Log "... starting -Command `"$Command`""
#runas /machine:x86 /trustlevel:0x20000 "C:\Windows\sysWOW64\cmd.exe /c `"$Command`""
# Invoke-Command -Credential $credential -ComputerName $env:ComputerName `
# -ScriptBlock { & $Command }
Log '... started? elevated?'
}
}
catch
{
Log "*** error starting $Name"
Log "*** $($_)"
}
}
else
{
Log "... could not file $credfile, aborting"
}
}
function RegisterTask
{
$span = $delay.ToString()
$cmd = "Restart-App -Name '$Name' -Command '$Command' -Arguments '$cargs' -User $User -delay '$span'"
$trigger = New-ScheduledTaskTrigger -Daily -At $StartTime
$action = New-ScheduledTaskAction -Execute 'pwsh' -Argument "-Command ""$cmd"""
$task = Get-ScheduledTask -TaskName "Restart $Name" -ErrorAction:SilentlyContinue
if ($task -eq $null)
{
Write-Host "... creating scheduled task 'Restart $Name'"
$credential = New-Object PSCredential($User, $Password)
$credential | Export-Clixml -Path "C:\Users\$User\$Name`.xml" -Force
#$credential = (New-Object System.Management.Automation.PSCredential `
# -ArgumentList $User, $Password).GetNetworkCredential()
$plainPwd = ($credential).GetNetworkCredential().Password
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "Restart $Name" `
-User $User -Password $plainPwd | Out-Null
}
else
{
Write-Host "... scheduled task 'Restart $Name' is already registered"
}
}
function UnregisterTask
{
$taskname = "Restart $Name"
$info = get-scheduledtaskinfo -taskname $taskName -ErrorAction:SilentlyContinue
if ($info)
{
Write-Host "... unregistering scheduled task '$taskName'"
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
else
{
Write-Host "... scheduled task '$taskName' is not found"
}
}
function Log
{
param([string] $text)
$text | Out-File -FilePath $LogFile -Append
}
}
Process
{
if ($Name -and $Unregister)
{
UnregisterTask
return
}
if ($GetCommand)
{
GetCommandLine
return
}
if (![TimeSpan]::TryParse($Delay, [ref]$delay))
{
Write-Host '*** Invalid Delay parameter, must be in TimeSpan format'
return
}
if ($Register)
{
if (!$Command)
{
Write-Host '... Command argument is required when registering' -ForegroundColor Yellow
return
}
RegisterTask
return
}
$script:LogFile = Join-Path $env:TEMP 'restart-app.log'
# reset the log file
"starting at $(Get-Date)" | Out-File -FilePath $LogFile
Shutdown
Startup
Log 'done'
}