Click here to Skip to main content
15,036,891 members
Articles / Web Development / ASP.NET / ASP.NET Core
Technical Blog
Posted 5 Aug 2021

Stats

2.2K views
3 bookmarked

How to Close Window with PowerShell Core

Rate me:
Please Sign up or sign in to vote.
4.20/5 (3 votes)
5 Aug 2021CPOL4 min read
PowerShell script that runs command and confirms operation by closing confirmation window
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.

Image 1

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:

PowerShell
param (
    # command to run
    [Parameter(Mandatory = $true)]
    [ScriptBlock]
    $Command = { Write-Host 'Run command'; },

    # name of the window to close
    [Parameter(Mandatory = $true)]
    [string]$WindowName,

    # number of attempts
    [Parameter(Mandatory = $false)]
    [Int16]$MaxAttempts = 10,

    # delay in seconds
    [Parameter(Mandatory = $false)]
    [Int16]$Delay = 5
)

# script seeks for the windows and send keys
$closeWindowJob = {
    param (
        # name of the window to close
        [Parameter(Mandatory = $true)]
        [string]$WindowName,

        # number of attempts
        [Parameter(Mandatory = $false)]
        [Int16]$MaxAttempts = 10,

        # delay in seconds
        [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 all jobs in the current session
Get-Job;
$attempt = 0;
# Wait for all jobs to complete
While ((Get-Job -State "Running") -and ($attempt -lt $MaxAttempts)) {
    Write-Verbose "#$($attempt). Sleep $Delay seconds";
    Start-Sleep $Delay;
    ++$attempt;
}

# use job Id as the current session could contain more than 1 job with the name
$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; # | Receive-Job;
}

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:

PowerShell
# script runs dotnet command
$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.

  1. command-job, defined on the line #56, runs the command passed as the parameter $Command.
  2. close-window-job, defined on the line #58, runs the close window command and passes parameters from the main script.
PowerShell
$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.

PowerShell
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.

PowerShell
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

  1. All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
  2. Information is provided «AS IS».

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Illya Reznykov
Software Developer (Senior)
Ukraine Ukraine
• Have more than 25 years of the architecting, implementing, and supporting various applications from small desktop and web utilities up to full-fledged cloud SaaS systems using mainly Microsoft technology stack and implementing the best practices.
• Have significant experience in the architecting applications starting from the scratch and from the existent application (aka “legacy”) where it is required to review, refactor, optimise the codebase and data structure, migrate to new technologies, implement new features, best practices, create tests and write documentation.
• Have experience in project management, collecting business requirements, creating MVP, working with stakeholders and end users, and tasks and backlog management.
• Have hands-on experience in the setting up CI/CD pipelines, the deploying on-premise and cloud systems both in Azure and AWS, support several environments.
• As Mathematician, I interested much in the theory of automata and computer algebra.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Vincent Radio5-Aug-21 23:35
professionalVincent Radio5-Aug-21 23:35 
GeneralRe: My vote of 5 Pin
Illya Reznykov24-Aug-21 2:04
MemberIllya Reznykov24-Aug-21 2:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.