PowerShell - Prompt for 'Would you like to continue' - powershell

I have a script I am using to automate WSUS processes and the last stage of it goes on to remove all old/unnecessary files/objects.
I would like to prompt 'Press 'enter' to continue with removal or any other key to stop' before the cleanup stage to give people the option to not run it.
The code I currently have at the end of the script is here:
Get-WsusServer 10.1.1.25 -PortNumber 8530 | Get-WsusUpdate -Classification All -Approval Unapproved -Status FailedOrNeeded | Approve-WsusUpdate -Action Install -Target $ComputerTarget -Verbose
Write-Host "Updates have been approved!"
Write-Host "Preparing to clean WSUS Server of obsolete computers, updates, and content files."
#Part2 - WSUS Server Cleanup
##Run Cleanup Command
Get-WsusServer $WSUS_Server -PortNumber $PortNumber | Invoke-WsusServerCleanup -CleanupObsoleteComputers -CleanupObsoleteUpdates -CleanupUnneededContentFiles
Just prior to #Part2 I would like to have the prompt 'Press enter to continue or any other key to abort'
I can't seem to find a simple way to do this? everything I've seen appears to involve nesting the entire script inside of a code block which I'd rather not do. =/
Thanks!

You can prompt the user like this:
$response = read-host "Press enter to continue or any other key (and then enter) to abort"
If the user just presses enter, then the $response will be empty. Powershell will convert an empty string to boolean false:
$aborted = ! [bool]$response
Or you can just query for a particular character:
$response = read-host "Press a to abort, any other key to continue."
$aborted = $response -eq "a"

So something I keep on hand is a Show-MsgBox function to toss into scripts. That way I can show a dialog box at will with a simple command, and it has options for what buttons to show, icons to display, window title, and text in the box.
Function Show-MsgBox ($Text,$Title="",[Windows.Forms.MessageBoxButtons]$Button = "OK",[Windows.Forms.MessageBoxIcon]$Icon="Information"){
[Windows.Forms.MessageBox]::Show("$Text", "$Title", [Windows.Forms.MessageBoxButtons]::$Button, $Icon) | ?{(!($_ -eq "OK"))}
}
That's all there is to the function, then in your case you could do something like:
If((Show-MsgBox -Title 'Confirm CleanUp' -Text 'Would you like to continue with the cleanup process?' -Button YesNo -Icon Warning) -eq 'No'){Exit}
Then it pops up with Yes and No buttons and if they click No it exits the script.

This isn't perfect, but it will give your user a chance to escape the script. It might actually be better because it means your user won't accidentally hit the backslash button and cancel the script when they wanted to press enter.
Write-Host "Press `"Enter`" to continue or `"Ctrl-C`" to cancel"
do
{
$key = [Console]::ReadKey("noecho")
}
while($key.Key -ne "Enter")
Write-Host "Complete"

Related

Powershell 5.0 pause function [duplicate]

Disclaimer : I am the epitome of a scipting/Powershell rookie, so please bear with me.
I've written a script to return the Active Directory username of any user currently logged into a given workstation.
$input = Read-Host "Workstation Name"
$domain = ".*****.***.com"
$computer = $input + $domain
$list = gwmi win32_computersystem -comp $computer | select Username,Caption
Write-Output $list
However, if I run this from a pinned script in the taskbar, the Powershell window closes before I have a chance to view the results.
I have tried method 2 and 3 from this post, but to no avail. Method 2 prompts for user input before the results are displayed instead of after, even when the code for the prompt is added at the end of the script.
Any help would be greatly appreciated.
Method 2 from the linked post - i.e., waiting for the user to press a key before exiting the script - can be used, but it requires additional effort:
End your script as follows in order to see the value of $list before the pause command prompts:
$list | Out-Host # Force *synchronous* to-display output.
pause # Wait for the user to press Enter before exiting.
Note: pause in PowerShell is simply a function wrapper around Read-Host as follows: $null = Read-Host 'Press Enter to continue...' Therefore, if you want to customize the prompt string, call Read-Host directly.
This answer explains why the use of Out-Host (or Format-Table) is necessary in this case; in short:
In PSv5+, an implicitly applied Format-Table command asynchronously waits for up to 300 msecs. for additional pipeline input, in an effort to derive suitable column widths from the input data.
Because you use Write-Output output objects without predefined formatting data that have 2 properties (4 or fewer ), tabular output is implicitly chosen, and Format-Table is used behind the scenes, asynchronously.
Note: The asynchronous behavior applies only to output objects for whose types formatting instructions aren't predefined (as would be reported with Get-FormatData <fullOutputTypeName>); for instance, the output format for the System.Management.Automation.AliasInfo instances output by Get-Alias is predefined, so Get-Alias; pause does produce output in the expected sequence.
The pause command executes before that waiting period has elapsed, and only after you've answered the prompt does the table print, after which point the window closes right away.
The use of an explicit formatting command (Out-Host in the most generic case, but any Format-* cmdlet will do too) avoids that problem by producing display output synchronously, so that the output will be visible by the time pause displays its prompt.
I had the same problem for scripts that I'm executing "on demand". I tend to simply add a Read-Host at the end of the script like so
$str = "This text is hardly readable because the console closes instantly"
Write-Output $str
Read-Host "Script paused - press [ENTER] to exit"

How do I prevent Powershell from closing after completion of a script?

Disclaimer : I am the epitome of a scipting/Powershell rookie, so please bear with me.
I've written a script to return the Active Directory username of any user currently logged into a given workstation.
$input = Read-Host "Workstation Name"
$domain = ".*****.***.com"
$computer = $input + $domain
$list = gwmi win32_computersystem -comp $computer | select Username,Caption
Write-Output $list
However, if I run this from a pinned script in the taskbar, the Powershell window closes before I have a chance to view the results.
I have tried method 2 and 3 from this post, but to no avail. Method 2 prompts for user input before the results are displayed instead of after, even when the code for the prompt is added at the end of the script.
Any help would be greatly appreciated.
Method 2 from the linked post - i.e., waiting for the user to press a key before exiting the script - can be used, but it requires additional effort:
End your script as follows in order to see the value of $list before the pause command prompts:
$list | Out-Host # Force *synchronous* to-display output.
pause # Wait for the user to press Enter before exiting.
Note: pause in PowerShell is simply a function wrapper around Read-Host as follows: $null = Read-Host 'Press Enter to continue...' Therefore, if you want to customize the prompt string, call Read-Host directly.
This answer explains why the use of Out-Host (or Format-Table) is necessary in this case; in short:
In PSv5+, an implicitly applied Format-Table command asynchronously waits for up to 300 msecs. for additional pipeline input, in an effort to derive suitable column widths from the input data.
Because you use Write-Output output objects without predefined formatting data that have 2 properties (4 or fewer ), tabular output is implicitly chosen, and Format-Table is used behind the scenes, asynchronously.
Note: The asynchronous behavior applies only to output objects for whose types formatting instructions aren't predefined (as would be reported with Get-FormatData <fullOutputTypeName>); for instance, the output format for the System.Management.Automation.AliasInfo instances output by Get-Alias is predefined, so Get-Alias; pause does produce output in the expected sequence.
The pause command executes before that waiting period has elapsed, and only after you've answered the prompt does the table print, after which point the window closes right away.
The use of an explicit formatting command (Out-Host in the most generic case, but any Format-* cmdlet will do too) avoids that problem by producing display output synchronously, so that the output will be visible by the time pause displays its prompt.
I had the same problem for scripts that I'm executing "on demand". I tend to simply add a Read-Host at the end of the script like so
$str = "This text is hardly readable because the console closes instantly"
Write-Output $str
Read-Host "Script paused - press [ENTER] to exit"

How can I set the save location of a file using powershell?

I am trying to print a PDF as XPS, the script opens the PDF Print Output As screen and enters the correct name before sending the ENTER command to the window to save the file.
How can I select the address bar to enter the desired path? Or how can I change the default save path?
EDIT: Thank you for the feedback. Here is the script:
function print_files($secure_pdf_dir){
#Retrieves the name for the .xps files
Get-ChildItem $secure_pdf_dir -Filter *.pdf -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $_.FullName
}
}
## The following function keeps checking for a new window called "Save Print Output As"
## When the window shows up, it enters the name of the file and press ENTER
function enter_my_names($xps_dir, $fullname){
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys($xps_dir\$basename)
$wshell.SendKeys("{ENTER}")
}
## The following function launches simultaneously a print job on the input file
## and a function waiting for the print job to show up to name the file
workflow same_time{
Param(
$fullname
)
parallel{
Start-Process -FilePath $fullname –Verb Print -PassThru
enter_my_names $xps_dir $fullname
}
}
#MAIN PROGRAM
#Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
#Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
#Sets the XPS printer as Default
$printer.SetDefaultPrinter()
#Starts the main job
print_files($secure_pdf_dir)
#Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
#This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 2
#Finally, close Adobe Reader
Get-Process "acrord32" | Stop-Process
It seems that the $xps_dir variable is not being passed into the function properly.
Edit: Error I get when trying to add $xps_dir to the enter_my_names function:
Microsoft.PowerShell.Utility\Write-Error : Cannot validate argument on parameter
'FilePath'. The argument is null or empty. Provide an argument that is not null or
empty, and then try the command again.
At same_time:115 char:115
+
+ CategoryInfo : NotSpecified: (:) [Write-Error], ParameterBindingValidati
onException
+ FullyQualifiedErrorId : System.Management.Automation.ParameterBindingValidationEx
ception,Microsoft.PowerShell.Commands.WriteErrorCommand
+ PSComputerName : [localhost]
I'm not sure if you got this fully worked out or I misread your post, but I was able to implement some code from your initial post to finish my project [been beating my head against a wall as a non-traditional comp sci person]; so I wanted to share some things I did in hopes of helping you (in the event you're still at that crossroads).
Some points of interest:
-My overall process is a bulk report generator. A variable amount of .XML stylesheets are sent to IE because a javascript subroutine numbers the reports; to accomplish this I have visual basic doing some database parsing, passing values into custom scripts needed to form the .XML stylesheets, then VB creates PS .ps1 files with the various PS commands needed to create the IE com object. Finally, VB calls the .ps1 scripts, and the reports are generated by sending the stylesheet to IE, waiting for HTML render, send print command, and close the object.
-While your endgoal is to generate an XPS, mine was to fully render an HTML, then print it with a PDF printer; I assume we have a similar process in that we must engage a dialog box in the intermediate stage (in this case, using sendkeys to interact with it); this is specifically what I'm going to discuss below in hopes of helping out!
So, two points of discussion:
First, more of a general observation/query: I don't see where your $xps_dir variable is actually being defined; I mention this because there could be a string-related issue with the values being passed by the $xps_dir. One way to check your string to make sure its 'pretty' is to pass it to a .txt or something using the OUT-FILE command:
"$xps_dir" | Out-File -FilePath "C:\somefolder\sometext.txt" -Append
The text file itself doesn't need to exist, but the file does...the above command will create the text file.
Also, if you're ok with viewing the powershell prompt, a WRITE-HOST can be used.
Either way, I'm wondering if the final compiled string isn't compiled correctly, so its not recognizing it as a directory.
I think what more likely is happening is the SENDKEYS aren't being sent to the correct fields. For example, by using your code I was [FINALLY] able to set my 'Save As' dialog box as an object and interact with it. (When using my PDF printer, I get the 'Save As' dialog box).
At first, I tried to send the file name, then send enter. I assumed this would be fine, because during manual interaction the focus for the 'Save As' dialog box goes to the 'File Name' field; however, after trying:
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{ENTER}")
I realized the SendKeys command was sending the keys to the 'Save in' drop down (or probably the first element of the Save As dialog box). So, I manually click inside of the 'Save in' field, then tabbed around until I got to 1. 'File Name' (wrote the # of tabs down), and 2. the 'Save' button (wrote # of tabs down). By adding SendKeys("{TAB}") to correspond with the number of tabs I observed, I was able to successfully enter my name string and close out the print dialog box:
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{ENTER}")
NOTE: the number of tabs might be different for your dialog box, so I strongly recommend doing a manual run through to count how many tabs get you to the field you want to interact with.
In conclusion, I have one reflection for consideration:
I've used (to much success) VB and PS to use html elements to dynamically enhance my routines. Since we're able to set the dialog box as a Com object, my next goal will be to try to tap into the respective 'elements', similar to how html elements can be accessed (or more generally, object-oriented language allows). This way, I might be able to better interact with the dialog box without suffering the drawbacks of SendKeys:
Has to run as active process; user cannot engage any other windows else keys might be sent to them;
Keys are based on a count; any variation to that count will require the subroutine to be updated/edited, making it not elastic;
Also, I have not done any work regarding error windows, but one I've already noticed is the "your document has the same name blah blah", so just a heads up.
Even though we're doing two different things, here's my final code for this part of my routine in case it helps provide context:
#send command to internet explorer application with execWB; 6 for print, 2 for no user prompt on window
while ( $ie.busy ) { Start-Sleep -second 3 }
$ie.execWB(6,2)
#create dialog box object
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save As') -ne $true){ Start-Sleep -second 3 }{
$wshell.AppActivate('Save As')
}
#create file string from 3 strings: directory,basename returned, file extension
#the return from my output log is: "c:\test_folder\mybasenamestring.PDF"
$mydirectory = "c:\test_folder\"
$mybasename = [io.path]::GetFileNameWithoutExtension($fullname)
$myextension = ".PDF"
$myfilename = "$mydirectory$mybasename$myextension"
#Using tabs to navigate around the dialog window; send my string to the 'File Name' field, then tab to 'Save' and send enter
#This is where the name is actually entered
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{ENTER}")
#going to add a command to report on the file size of the pdf; the pdf will stay 0kb until its fully generated. Once fully generated will add the $ie.quit or the kill command here for the $ie object
#this sends a stop process command to kill powershell since I'm running it as a ps1 file and it will remain open otherwise
stop-process -Id $PID
Last thing: I observed some weirdness with the capitalization, so I updated the final SendKeys to capitalize the string since I want all caps anyhow:
$wshell.SendKeys($myfilename.toupper())
Hope this helps! Thanks for posting; I was finally able to complete my entire process. As I clean and improve, if I find improvements for this area I'll try to remember to share. Thanks for your help!
Pass the variable containing the desired directory ($xps_dir in this case) to each function in the process until it gets to the enter_my_names function where it can then be sent to the window with the $wshell.SendKeys("$xps_dir\$basename").
function print_files($xps_dir, $secure_pdf_dir){
#Retrieves the name for the .xps files
Get-ChildItem $secure_pdf_dir -Filter *.pdf -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $xps_dir $_.FullName
}
}
## The following function keeps checking for a new window called "Save Print Output As"
## When the window shows up, it enters the name of the file and press ENTER
function enter_my_names{
param ($xps_dir, $fullname)
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys("$xps_dir\$basename")
$wshell.SendKeys("{ENTER}")
}
## The following function launches simultaneously a print job on the input file
## and a function waiting for the print job to show up to name the file
workflow same_time{
Param(
$xps_dir, $fullname
)
parallel{
Start-Process -FilePath $fullname –Verb Print -PassThru
enter_my_names $xps_dir $fullname
}
}
#MAIN PROGRAM
#Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
#Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
#Sets the XPS printer as Default
$printer.SetDefaultPrinter()
#Starts the main job
print_files $xps_dir $secure_pdf_dir
#Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
#This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 2
#Finally, close Adobe Reader
Get-Process "acrord32" | Stop-Process
The file dialogue will accept a path in the file name field, so instead of sending the filename file.pdf send the full path to the file C:\folder\folder\file.pdf
EDIT:
You can send the folder path like so:
$wshell.SendKeys($xps_dir)
$wshell.SendKeys("{ENTER}")
$wshell.SendKeys($basename)
$wshell.SendKeys("{ENTER}")

Check if a Powershell script has frozen using a separate script

So I'm preforming some automated testing using powershell in jenkins. I'm testing a web application where I must fill out forms, retrieve values, etc.
It is all fine but the web app contains some pop up messages that appear every now and then, which causes the main script to freeze until they are manually closed in the application. Below is a link to a stack overflow thread with a similar problem.
Powershell Website Automation: Javascript Popup Box Freeze
I followed the advice of the first answer. I created a separate powershell script that is executing constantly and can tell if there is a pop up window present (as they have their own process ids, so if there is more then one iexplore process id present it must be a popup) and then uses sendkeys to close it.
Main Script example:
#start application
start C:\Users\Webapp
#start the monitor script
Start-Process Powershell.exe -Argumentlist "-file C:\Users\Monitor.ps1"
#Get app as object
$app = New-Object -ComObject Shell.Application
$ClientSelectPage = $app.Windows() | where {$_.LocationURL -like "http:webapp.aspx"}
#Input value to cause popup message
$MemNumberInput = $ClientSelectPage.Document.getElementByID("MemNum")
$MemNumberInput.Select()
$MemNumberInput.value = "22"
$FindBtn.click()
It is at this point my script will freeze (as pop up window appears to tell me info abotu the client I've inserted) If this popup can be seen as a process, the monitor code will close it.
Example of monitor
$i = 0
while($i -eq 0)
{
#Check what process are currently running under the webapps name
$Mainprocid = Get-Process | where {$_.mainWindowTitle -like "*webapp*" } | select -expand id
$Mainprocid.COUNT
$integer = [int]$Mainprocid
#If there is only one process, no action
if( $Mainprocid.count -eq 1)
{
echo "no popup"
}
else
{
if($integer -eq '0')
{
#If there are no processes close the script
$i = 1
echo "close process"
}
else
#If there are two processes one must be a pop, send 'enter' to the app
{
echo "POP UP!"
$title = Get-Process |where {$_.mainWindowTItle -like "*webapp*"}
#Code to sendkeys 'ENTER' to the application to close the popup follows here
}
}
}
However, for whatever reason, some pop ups cannot be found as processes and the monitor script is useless with them. These are few and far between, so I figured the best way was for the monitor script to check and see if the main script has frozen for a certain amount of time. If so, it can use the sendkeys method it does for the other popups.
Is there a way for me to check and see if the main script has frozen, from the monitor script? I understand I could pass a parameter from the main script every now and then, to let the monitor script know it is still active, but this seems like a messy way of doing it, and an alternative method would be preferable.
Both scripts are saved as .ps1 files.

Use powershell to populate dialog box from another program

I am writing a script to uninstall and reinstall a piece of software.
Annoyingly, you have to enter a password as part of the uninstall process. (i.e. the uninstaller pops up with a box, the user enters a password to continue)
I want to be able to detect the dialog box, and enter the password automatically - so the user doesn't have to do anything. I seem to remember there's a way to do this, but not sure how.
Figured it out:
$app = get-process | where {$_.mainwindowtitle -match "Admin Password"}
$processID = 0
foreach ($a in $app) {
if ($a.id -gt $processID)
{
$processID = $a.id
}
}
start-sleep -Milliseconds 500
[Microsoft.VisualBasic.Interaction]::AppActivate($processID)
[System.Windows.Forms.SendKeys]::SendWait("mypassword{ENTER}")
Works great inmy demo app. Trouble is the app I am trying to send the password to is protected from SendKeys.
D'oh. Back to the drawing board...