Pause on failure, retry input from CSV - powershell

I have 2 sections of PowerShell code that exit the script on failure. After thinking about it I realized it would make more sense to pause on a failure and allow the admin time to remediate... then when the any key is paused, retry the same line in the CSV. Here's how it looks now:
#check tools status first
Write-Host ""
Write-Host "Checking VMware Tools Status before proceeding." -foreground green
Write-Host ""
foreach ($item in $vmlist) {
$vmname = $item.vmname
$ToolsStatus = (Get-VM $vmname).extensiondata.Guest.ToolsStatus -eq "toolsNotRunning"
if ($ToolsStatus -eq $true) {
Write-Host ""
Write-Host "Tools is not installed or running on $vmname. Remediate on guest and restart the script" -foreground Yellow
Write-Host "Script will continue to exit until tools is running on $vmname" -foreground yellow
Write-Host ""
exit
} else {
Write-Host ""
Write-Host "Tools running on all VMs, script will continue" -foreground green
Write-Host ""
}
}
I know how to put the pause in $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown'), but I have no idea to do the loop so it retries.
I'm using similar code elsewhere to validate that VMs are powered up so this will work in both sections.
EDIT: Here's the re-worked script. Would this work?
foreach ($item in $vmlist) {
$vmname = $item.vmname
do {
$ToolsStatus = (Get-VM $vmname).extensiondata.Guest.ToolsStatus -eq "toolsNotRunning"
if ($ToolsStatus) {
Write-Host ""
Write-Host "Tools is not installed or running on $vmname." -Foreground yellow
Write-Host "Remediate VMware tools on $vmname and"
Write-host "Press any key to retry..."
$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
} until ($ToolsStatus -eq "PoweredOn")
Write-Host "Tools running on $vmname, script will continue" -Foreground green
}

Add a nested do..while loop like this:
foreach ($item in $vmlist) {
$vmname = $item.vmname
do {
$ToolsMissing = (Get-VM $vmname).extensiondata.Guest.ToolsStatus -eq "toolsNotRunning"
if ($ToolsMissing) {
Write-Host "Tools is not installed or ..." -Foreground yellow
$Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
}
} while ($ToolsMissing)
}
Write-Host "Tools running on all VMs, script will continue" -Foreground green

Related

Coloring output

I made a very very simple code that just checks a few regkeys. However to make it more nice to the eyes I was hoping that whenever it's False i can make it red and whenever it's True i can make it green.
I googled a bunch about this but couldn't find a clear solution for what i'm trying to accomplish. Any tips are very much appreciated.
Write-Host "Update Pending" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending'
""
Write-Host "Reboot Pending:" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
""
Write-Host "Reboot Required:" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
""
Write-Host "Pending File Rename Operations:" -ForegroundColor Cyan
Test-Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations'
""
Write-Host "Beschikbare Updates:" -ForegroundColor Cyan
Test-Path 'HKLM:\SOFTWARE\Microsoft\Updates\UpdateExeVolatile'
Save the output from Test-Path to a variable, the use the value to specify one color or the other to pass as an argument to Write-Host:
Write-Host "Update Pending" -ForegroundColor Cyan
$testResult = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending'
Write-Host $testResult -ForegroundColor #('Red', 'Green')[$testResult]
Write-Host ''
PowerShell will implicitly convert $False to 0 and $True to 1 when used in an array indexer, so $False results in 'Red' being picked.
Since you're basically repeating the same test operation every time, you can simplify your code by organizing the labels and registry keys into a hashtable and then only write the code to test and output once:
$testCases = [ordered]#{
"Update Pending" = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Services\Pending'
"Reboot Pending" = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
"Reboot Required" = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
"Pending File Rename Operations" = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations'
"Beschikbare Updates" = 'HKLM:\SOFTWARE\Microsoft\Updates\UpdateExeVolatile'
}
foreach($label in $testCases.psbase.Keys){
Write-Host "${label}:" -ForegroundColor Cyan
$testResult = Test-Path $testCases[$label]
Write-Host $testResult -ForegroundColor #('Red','Green')[$testResult]
Write-Host ''
}

Test-NetConnection being executed in switch when it should not

I have a powershell script that uses a menu and a selection. So loops until selection is 0. For one selection it tests entered port with "Test-NetConnection". But for some reason after this is ran, whenever another selection is made it seems "Test-NetConnection" is always ran, the banner shows up in powershell (even though it is very quick, but it shouldn't be executed.
I removed some selections. And I also have a few custom functions. But not sure why this is happening. Any help would be greatly appreciated!
Powershell...
while ($selection -ne 0) {
Write-Output ""
Write-Output "__MENU__"
Write-Output "0. Exit"
Write-Output "1. Set Target Machines"
Write-Output "2. Set Save Directory"
Write-Output "4. Save MSINFO32"
Write-Output "5. Save All Installed Software"
Write-Output "9. Save GPO Info (gpresult)
Write-Output "10. Test WMI"
Write-Output "11. Test TCP Port"
Write-Output "14. Test Syslog"
Write-Output ""
Write-Output "Target Machines: $($targetComputers -Join ',')"
Write-Output "Save Directory: $savePath`n"
$selection = Read-Host -Prompt "Selection"
Write-Output ""
switch ($selection) {
0 { break }
1 { ... }
2 { ... }
11 {
$testPort = Read-Host -Prompt "TCP Port to Test (Leave blank for default: 80)"
Write-Output ""
if (($testPort -eq $null) -Or ($testPort -eq "")) { $testPort = 80 }
foreach ($computer in $targetComputers) {
try {
$netcon = Test-NetConnection -ComputerName $computer -Port $testPort -ErrorAction "Stop"
if($netcon.TcpTestSucceeded) { Write-Output "TCP Port $testPort test was successful on $computer." }
else { Write-Warning "TCP Port $testPort test was unsuccessful on $computer." }
}
catch { Write-Warning "TCP Port $testPort test was unsuccessful on $computer." }
}
} #11
14 {
$sysServer = Read-Host -Prompt "Syslog Server (Leave blank for default: ${env:computername})"
$sysProto = Read-Host -Prompt "Syslog Server Protocol (Leave blank for default: TCP)"
$sysPort = Read-Host -Prompt "Syslog Server Port (Leave blank for default: 514)"
Write-Output ""
if (($sysServer -eq $null) -Or ($sysServer -eq "")) { $sysServer = $env:computername }
if (($sysProto -eq $null) -Or ($sysProto -eq "")) { $sysProto = "TCP" }
if (($sysPort -eq $null) -Or ($sysPort -eq "")) { $sysPort = 514 }
sendSyslog -Server $sysServer -Protocol $sysProto -Port $sysPort
} #14
default { break }
} #switch
} #while
Your menu option "9. Save GPO Info (gpresult) is missing a closing quote.
Make this easier for yourself and use a Here-String for the menu:
$menu = #"
__MENU__
0. Exit
1. Set Target Machines
2. Set Save Directory
4. Save MSINFO32
5. Save All Installed Software
9. Save GPO Info (gpresult)
10. Test WMI
11. Test TCP Port
14. Test Syslog
"#
Then instead of looping like while ($selection -ne 0), run an endless loop you can exit as soon as the user types a '0'.
# enter an endless loop here.
# exit that loop right after the Read-Host when the user type '0'
while ($true) {
Write-Host $menu
# some in-between code.. #
$selection = Read-Host -Prompt "Selection"
if ($selection -eq '0') { break } # or exit if you want to stop the entire script here
# rest of your code.. #
}
P.S. anything coming back from Read-Host is a string, not a number.

Jumping to another section in a PowerShell script

I want to jump from one section in the script to another section in the same PowerShell script. My script is only going from top to bottom right now, but I have several tasks. e.g. I want to be able to choose from a task list in the beginning of the script and go to task number 2 skip task 1.
I hope this makes any sense for you. Take a look at my script:
Write-Host -ForegroundColor Yellow "welcome to my powershell script..."
""
""
""
Start-Sleep -Seconds 1
Write-Host -ForegroundColor Yellow "Choose a task:"
""
""
Write-Host -ForegroundColor Yellow "1. Clean up"
Write-Host -ForegroundColor Yellow "2. Uninstall Pre-Installed Apps"
Write-Host -ForegroundColor Yellow "3. Something should be written here"
""
""
""
While ($Valg -ne "1" -or $Valg -ne "2" -or $Valg -ne "3") {
$Valg = Read-Host "Choose a number from the task list"
If ($Valg –eq "1") { Break }
If ($Valg –eq "2") { Break }
If ($Valg –eq "3") { Break }
if ($Valg -ne "1" -or $Valg -ne "2" -or $Valg -ne "3") {
""
Write-Host -ForegroundColor Red "Ups. Try again..."
}
}
#### 1. First task should come here (clean up)
#some code here for the "cleaning up" task
#### 2. Second task here
#some code here for the "Uninstall Pre-Installed Apps" task
#### 3. Third task there
#Some code for the third task here
#### And so on...
Here is a generic solution which uses an array of PSCustomObject that represents the task (message and the function to invoke). It doesn't need a switch, you can simply add new Tasks to the array (and implement the desired function) without modifying the remaining script:
# define a task list with the messages to display and the functions to invoke:
$taskList = #(
[PSCustomObject]#{Message = 'Clean up'; Task = { Cleanup }}
[PSCustomObject]#{Message = 'Uninstall Pre-Installed Apps'; Task = { Uninstall }}
[PSCustomObject]#{Message = 'Something should be written here'; Task = { Print }}
)
# define the functions:
function Cleanup()
{
Write-Host "Cleanup"
}
function Uninstall()
{
Write-Host "Uninstall"
}
function Print()
{
Write-Host "Print"
}
# let the user pick a task:
Write-Host -ForegroundColor Yellow "Choose a task:"
$taskList | foreach -Begin { $i = 1;} -Process {
Write-Host -ForegroundColor Yellow ('{0}. {1}' -f ($i++), $_.Message)
}
do
{
$value = Read-Host 'Choose a number from the task list'
}
while($value -match '\D+' -or $value -le 0 -or $value -gt $taskList.Count)
# invoke the task:
& $taskList[$value-1].Task
I am updating answer to complete script based on inputs by "bluuf" and "majkinator".
Use the Switch-Case construct along with Functions like below. This is complete working solution.
#region Function Definitions. These come first before calling them
function FirstTask
(
[string] $inputVariable
)
{
"write any scipt for First task here without quotes. Input is: " + $inputVariable
}
function SecondTask
{
"write any scipt for Second task here without quotes"
}
function ThirdTask
{
"write any scipt for Third task here without quotes"
}
#endregion
#region Showing Options
Write-Host -ForegroundColor Yellow "welcome to my powershell script..."
""
""
""
Start-Sleep -Seconds 1
Write-Host -ForegroundColor Yellow "Choose a task:"
""
""
Write-Host -ForegroundColor Yellow "1. Clean up"
Write-Host -ForegroundColor Yellow "2. Uninstall Pre-Installed Apps"
Write-Host -ForegroundColor Yellow "3. Something should be written here"
Write-Host -ForegroundColor Yellow "0. Exit"
""
""
""
#endregion
#region Getting input
While ($true) {
$Valg = Read-Host "Choose a number from the task list"
If ($Valg –eq "0")
{
"Thanks for using my utility";
Break;
}
If (($Valg -ne "1") -and ($Valg -ne "2") -and ($Valg -ne "3") -and ($Valg -ne "0")) {
""
Write-Host -ForegroundColor Red "Ups. Try again..."
}
#region Main Code
switch ($Valg)
{
1{
FirstTask("sending input");
}
2 {
SecondTask;
}
3 {
ThirdTask;
}
default { "Please select a correct option."}
}
#endregion
}
#endregion

Modifying SSL cert check Powershell script to loop through multiple sites

I'm a Powershell newb, but I am trying to write a script to check the SSL certificate expiration dates for multiple remote websites.
I found this script (http://www.zerrouki.com/checkssl/) that does what I want, but only for a single site.
I am trying to modify it to allow for multiple sites/checks, but am getting an error when I do so. I've removed all of the email functionality from the script as I'll be using another to tool to alert on expiring certs. And I've hardcoded the URLs to check.
<#
Modified from Fabrice ZERROUKI - fabricezerrouki#hotmail.com Check-SSL.ps1
#>
$WebsiteURLs= #("URL1.com","URL2.com","URL3.com")
$WebsitePort=443
$CommonName=$WebsiteURL
$Threshold=120
foreach ($WebsiteURL in $WebsiteURLs){
Try{
$Conn = New-Object System.Net.Sockets.TcpClient($WebsiteURL,$WebsitePort)
Try {
$Stream = New-Object System.Net.Security.SslStream($Conn.GetStream())
$Stream.AuthenticateAsClient($CommonName)
$Cert = $Stream.Get_RemoteCertificate()
$ValidTo = [datetime]::Parse($Cert.GetExpirationDatestring())
Write-Host "`nConnection Successfull" -ForegroundColor DarkGreen
Write-Host "Website: $WebsiteURL"
$ValidDays = $($ValidTo - [datetime]::Now).Days
if ($ValidDays -lt $Threshold)
{
Write-Host "`nStatus: Warning (Expires in $ValidDays days)" -ForegroundColor Yellow
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor Yellow
}
else
{
Write-Host "`nStatus: OK" -ForegroundColor DarkGreen
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor DarkGreen
}
}
Catch { Throw $_ }
Finally { $Conn.close() }
}
Catch {
Write-Host "`nError occurred connecting to $($WebsiteURL)" -ForegroundColor Yellow
Write-Host "Website: $WebsiteURL"
Write-Host "Status:" $_.exception.innerexception.message -ForegroundColor Yellow
Write-Host ""
}
}
When I run this (with valid sites in the $WebsiteURLs variable) every site returns: Status: Authentication failed because the remote party has closed the transport stream.
If I only put one site in the $WebsiteURLs variable and remove the foreach function it runs ok.
Any idea what I can do to make this loop through each site in the variable?
Problem lies here:
$WebsiteURLs= #("URL1.com","URL2.com","URL3.com")
$WebsitePort=443
$CommonName=$WebsiteURL
When you call $Stream.AuthenticateAsClient($CommonName) it doesn't work, because $CommonName=$WebsiteURL is setting $commonName to null. When you remove the loop I assume you did as I did and changed $WebsiteURLs to $WebsiteURL so then you had a value to assign $CommonName.
If you move the declaration of $CommonName to within your loop it works.
$WebsiteURLs= #("URL1.com","URL2.com","URL3.com")
$WebsitePort=443
$Threshold=120
foreach ($WebsiteURL in $WebsiteURLs){
$CommonName=$WebsiteURL
Try{
$Conn = New-Object System.Net.Sockets.TcpClient($WebsiteURL,$WebsitePort)
Try {
$Stream = New-Object System.Net.Security.SslStream($Conn.GetStream())
$Stream.AuthenticateAsClient($CommonName)
$Cert = $Stream.Get_RemoteCertificate()
$ValidTo = [datetime]::Parse($Cert.GetExpirationDatestring())
Write-Host "`nConnection Successfull" -ForegroundColor DarkGreen
Write-Host "Website: $WebsiteURL"
$ValidDays = $($ValidTo - [datetime]::Now).Days
if ($ValidDays -lt $Threshold)
{
Write-Host "`nStatus: Warning (Expires in $ValidDays days)" -ForegroundColor Yellow
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor Yellow
}
else
{
Write-Host "`nStatus: OK" -ForegroundColor DarkGreen
Write-Host "CertExpiration: $ValidTo`n" -ForegroundColor DarkGreen
}
}
Catch { Throw $_ }
Finally { $Conn.close() }
}
Catch {
Write-Host "`nError occurred connecting to $($WebsiteURL)" -ForegroundColor Yellow
Write-Host "Website: $WebsiteURL"
Write-Host "Status:" $_.exception.innerexception.message -ForegroundColor Yellow
Write-Host ""
}
}

powershell to Ping,RDP,RemoteRegistry,WMI

I wrote a Powershell script that will perform Ping, RDP(3389), Remote Registry and WMI connectivity to a remote computer. Below is the script that I have come up with. Now I would like to get the output in a short format such as:
PING : SUCCESS
RDP : FAIL
Remote Registry : FAIL
WMI : SUCCESS
Remote Registry check has FAILED.
RDP check has FAILED.
PING check SUCCEEDED
WMI check SUCCEEDED
I am looking for help as I am new to Powershell scripting. Thank you in advance!!
Write-Host `n
$inputfromuser = Read-Host "Enter Server Name"
If($inputfromuser -like "")
{
Write-Host "User Input cannot be blank. Please enter the server name"
Write-Host `n
}
else
{
Write-Host `n
Write-Host -ForegroundColor Yellow "CHECKING PING CONNECTIVITY TO SERVER $inputfromuser"
Test-Connection -ComputerName $inputfromuser -Quiet -Count 1
Write-Host `n
Write-Host -ForegroundColor Yellow "CHECKING RDP PORT 3389 CONNECTIVITY ...."
Test-NetConnection -ComputerName $inputfromuser -CommonTCPPort RDP -InformationLevel Quiet
Write-Host `n
Write-Host -ForegroundColor Yellow "CHECKING REMOTE REGISTRY CONNECTIVITY ...."
$regkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$inputfromuser)
$ref = $regkey.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall")
If (!$ref) {
Write-Host -ForegroundColor Red "REMOTE REGISTRY CHECK FAILED"
}
Else {
Write-Host -ForegroundColor Green "REMOTE REGISTRY CHECK SUCCESS"
}
Write-Host `n
Write-Host -ForegroundColor Yello "CHECKING REMOTE WMI CONNECTIVITY ...."
$wmi = GWMI -Query "Select * from Win32_PingStatus where Address = '$inputfromuser'"
If (!$wmi) {
Write-Host -ForegroundColor Red "REMOTE WMI CHECK FAILED"
}
Else {
Write-Host -ForegroundColor Green "REMOTE WMI CHECK SUCCESS"
}
Write-Host `n
}
I'd recommend separating tests from output creation. Assign the results of your checks to separate variables:
$ping = Test-Connection ...
$rdp = Test-NetConnection ...
$regkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $inputfromuser)
$ref = $regkey.OpenSubKey("...")
$wmi = Get-WmiObject -Query "..."
create 2 hashtables:
$state_noun = #{
$true = 'SUCCESS'
$false = 'FAIL'
}
$state_verb = #{
$true = 'SUCCEEDED'
$false = 'FAILED'
}
and create the output with a here-string:
$result = #"
PING : $($state_noun[$ping])
RDP : $($state_noun[$rdp])
Remote Registry : $($state_noun[[bool]$ref])
WMI : $($state_noun[[bool]$wmi])
Remote Registry check has $($state_verb[[bool]$ref]).
RDP check has $($state_verb[$rdp]).
PING check $($state_verb[$ping])
WMI check $($state_verb[[bool]$wmi])
"#
Write-Host $result
If you must have highlighted/colored values in your output, use a custom output function like this:
function Write-Result {
[CmdletBinding()]
Param(
[Parameter()][string]$text,
[Parameter()][bool]$value
)
Write-Host $text -NoNewline
if ($value) {
Write-Host $value -NoNewline -ForegroundColor Green
} else {
Write-Host $value -NoNewline -ForegroundColor Red
}
Write-Host '.'
}
Write-Result 'PING : ' $state_noun[$ping]
...
Write-Result 'Remote Registry check has ' $state_verb[[bool]$ref]
...