This post is devoted to PowerShell script which runs the command and confirms an operation by the closing confirmation window.
Introduction
Automation is a great approach to get rid of the manual work and PowerShell is a great scripting language. Unfortunately some applications require user input or confirmation. The post is devoted to the script written in PowerShell which runs the command and confirms an operation by the closing confirmation window.
Let’s note that script has several disadvantages.
Background
The solution uses PowerShell 7.1.3.
Problem
Let’s consider the PowerShell script that should be run in an unattended mode, but one of the commands shows the dialog window to ask a user to confirm or to cancel the operation. Obviously this window blocks the script until the user closes it.
I faced up with such an issue when try trust ASP.NET Core HTTPS development certificate by running the command:
dotnet dev-certs https --trust;
It gives the output and asks the user to confirm or to cancel the operation:
Trusting the HTTPS development certificate was requested.
A confirmation prompt will be displayed if the certificate
was not previously trusted. Click yes on the prompt to
trust the certificate.
Security Warning dialog to install a certificate
Solution
Let’s create the script close-windows.ps1
which solves this blocking issue.
PowerShell allows to write command which seeks the window and sends keys to it. It means that the main command and the close window command should be executed in parallel as background jobs. As jobs are started, the main script continues and waits until either jobs finish or timeout is expired. Then it stops hung jobs if any and outputs execution results.
There is a listing of the script close-windows.ps1
:
param (
[Parameter(Mandatory = $true)]
[ScriptBlock]
$Command = { Write-Host 'Run command'; },
[Parameter(Mandatory = $true)]
[string]$WindowName,
[Parameter(Mandatory = $false)]
[Int16]$MaxAttempts = 10,
[Parameter(Mandatory = $false)]
[Int16]$Delay = 5
)
$closeWindowJob = {
param (
[Parameter(Mandatory = $true)]
[string]$WindowName,
[Parameter(Mandatory = $false)]
[Int16]$MaxAttempts = 10,
[Parameter(Mandatory = $false)]
[Int16]$Delay = 5
)
Write-Host 'Creating a shell object';
$wshell = New-Object -ComObject wscript.shell;
for ($index = 0; $index -lt $MaxAttempts; $index++) {
Write-Host "#$($index). Seeking for a window";
$result = $wshell.AppActivate($WindowName);
if ($result) {
Write-Host 'Send keys';
$wshell.SendKeys('{TAB}');
$wshell.SendKeys('~');
break;
}
else {
Write-Host "Window '$WindowName' is not found";
Start-Sleep $Delay;
}
}
}
Write-Verbose 'start jobs';
$jobs = New-Object "System.Collections.ArrayList";
$job = Start-Job -Name 'command-job' -ScriptBlock $Command;
$jobs.Add($job) | Out-Null;
$job = Start-Job -Name 'close-window-job' -ScriptBlock $closeWindowJob -ArgumentList $WindowName, $MaxAttempts, $Delay;
$jobs.Add($job) | Out-Null;
Write-Host "Command job Id: $($jobs[0].Id)";
Write-Host "Close window job Id: $($jobs[1].Id)";
Get-Job;
$attempt = 0;
While ((Get-Job -State "Running") -and ($attempt -lt $MaxAttempts)) {
Write-Verbose "#$($attempt). Sleep $Delay seconds";
Start-Sleep $Delay;
++$attempt;
}
$job = $null;
foreach ($job in $jobs) {
$jobState = Get-Job -Id $job.Id;
if ($jobState.State -eq "Running") {
Stop-Job -Id $job.Id;
}
}
Write-Host 'Getting the information back from the jobs';
foreach ($job in $jobs) {
Get-Job -Id $job.Id;
}
As the script accepts a script block as a parameter, the command could be set by the another script. For example close-window.example.ps1
defines script block $Command
as the required command, $WindowName
as the name of a window that should be closed, and calls close-window.ps1
:
$Command = {
$inputFile = """$($Env:ProgramFiles)\dotnet\dotnet.exe""";
Write-Host "Run $inputFile";
Start-Process $inputFile -ArgumentList `
'dev-certs', 'https', '--trust' -Wait | Out-Host;
}
$WindowName = 'Security Warning';
.\close-window.ps1 `
-Command $Command `
-WindowName $WindowName `
-Verbose;
Let’s note that the script has some disadvantages:
Close-window.ps1 script
The script has the following parameters:
$Command
is the command that is run as the background job. $WindowName
is the name of the window to close, can't be null or empty string. $MaxAttempts
is the number of attempts to confirm the operation. It is the optional parameter and has default value equals 10 attempts. $Delay
is the delay in seconds between attempts. It is the optional parameter and has default value equals 5 seconds.
The close window command is defined at lines 21-52. The command block has parameters that allow to pass values from the main script as $WindowName
, $MaxAttempts
, $Delay
. It creates wscript.shell
object and seeks for a window by its name. To simulate thread synchronization the close window command runs the loop at most $MaxAttempt
times and seeks for a window. If window is not found it means that the main command is still in progress. If a window is found, the command sends key sequence, that in our case is Tab
, Enter
to move focus to Yes
button and click it.
A Windows PowerShell background job is a command that runs in the background without interacting with the current session. Typically, you use a background job to run a complex command that takes a long time to finish. For more information about background jobs in Windows PowerShell, see about_Jobs.
Two jobs are started and their ids are stored in an array because the current session could contains other jobs, possible with the same names.
command-job
, defined on the line #56, runs the command passed as the parameter $Command
. close-window-job
, defined on the line #58, runs the close window command and passes parameters from the main script.
$jobs = New-Object "System.Collections.ArrayList";
$job = Start-Job -Name 'command-job' -ScriptBlock $Command;
$jobs.Add($job) | Out-Null;
$job = Start-Job -Name 'close-window-job' -ScriptBlock $closeWindowJob -ArgumentList $WindowName, $MaxAttempts, $Delay;
$jobs.Add($job) | Out-Null;
As soon as jobs are executed in the background, the main script waits until the jobs do their work but is aware that jobs could hung. The statement Get-Job -State "Running"
returns $null
if there are no running jobs in the current session and could be used as a condition for the loop. If jobs are still running the main script waits $Delay
seconds and checks jobs once again. In order not to get an infinite loop let's run the loop no more than $MaxAttempts
times. The loop is defined at lines 68-72.
While ((Get-Job -State "Running") -and ($attempt -lt $MaxAttempts)) {
Write-Verbose "#$($attempt). Sleep $Delay seconds";
Start-Sleep $Delay;
++$attempt;
}
To clean up resources the main script checks job state for all running jobs. If it still running, the scripts stops the job at line #79.
if ($jobState.State -eq "Running") {
Stop-Job -Id $job.Id;
}
At the end of the script, the command Get-Job -Id $job.Id
returns job states.
References
There are several useful topics:
History
- 08/05/2021 - the article is published.
- 08/26/2021 - the script is extended: add parameters, timeouts, more checks, and add close-window.example.ps1 as a sample script.
Notes
- All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
- Information is provided «AS IS».