Coloring output - powershell

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 ''
}

Related

Comparing local file (Get-Content) with web file (Invoke-WebRequest) always results in not equal

Basically I'm writing an updater for my script. It's supposed to compare itself (Get-Content -Path $PSCommandPath) to the one on the web server (Invoke-WebRequest...). However, it always shows an updated is available.
My understanding (from other questions here) is that -raw will force it to return the file as a string which should be the same way the web request is returned. But I'm guessing the issue is somewhere in there. Hopefully a simple thing...
function checkUpdates() {
Write-Host "Checking for updates... " -NoNewline -ForegroundColor $clrMain
$item1 = Get-Content -Path $PSCommandPath -raw
$item2 = Invoke-WebRequest -Uri "$($config.remote)/launcher/launch.ps1"
if ($item1 -eq $item2) {
Write-Host "Up to date" -ForegroundColor $clrSuccess
} else {
Write-Host "New version available" -ForegroundColor $clrWarn
invoke-expression "./update.ps1 $($config.remote)"
exit
}
}
I was able to replace invoke-webrequest with curl and replace the comparison to accomplish what I wanted.
function checkUpdates() {
Write-Host "Checking for updates... " -NoNewline -ForegroundColor $clrMain
$item1 = Get-Content -Path $PSCommandPath
$item2 = curl "$($config.remote)/launcher/launch.ps1"
if (!(Compare-Object -ReferenceObject $item1 -DifferenceObject $item2)) {
Write-Host "Up to date" -ForegroundColor $clrSuccess
} else {
Write-Host "New version available" -ForegroundColor $clrWarn
}
}

Best way to create multiple lines with different colors

I am trying to figure out, what is the best way, to output multi-lines with different colors.
There are several options that I have but each of them is challenging in some way. The possibilities I assume I have are:
Listbox
Textbox
Label
I am building a vocabulary trainer, where during a test, you have to write words. At the end of the test, I would like to summarize the test.
The first challenge is, that the test are different in length, meaning it can be a test of only 10 words or more. The target is, to show to each question the answer and analyze if it is correct and wrong and if wrong, where it went wrong. That part I have solved.
But now the question is, how to present the results.
Listbox and Textbox are surely easier to handle but if I understand correctly, there will be a challenge with the output having different colors.
Label would be easier to handle but there I am struggling with Multiline and so far, the only solution I have so far is to generate for each result a different label, which means, I have to build labels according to the sum of the output, meaning 10 test questions would require 10 Labels.
When you run the test script below, you will see as a result, you wrote: Where the mistake is shown with a red-letter.
The idea would be, to have such output for each line, where the mistake is shown and if correct, it has a green color, to show, it is all ok.
Clear-Host
$Frage = 'Hello World'
$Antwort = 'Hello Warld'
$Script:Counter = 0
$wordlength = $Frage.Length
Do {
if ($Antwort[$Script:Counter] -ne $Frage[$Script:Counter]) { break }
Write-Host $Frage[$Script:Counter] -NoNewline;
Write-Host -ForegroundColor Red ' '$Antwort[$Script:Counter]
$Script:Counter++
} # End of 'Do'
Until ($Script:Counter -eq $wordlength)
If ($Antwort.Length -gt $Frage.Length) {
$TooShortDifference = $Antwort[($Frage.Length)..($Antwort.Length)]
Write-Host ''
Write-Host -ForegroundColor Red 'Your Answer has too many letters'
Write-Host ''
Write-Host -ForegroundColor Green -NoNewline 'You wrote: '
Write-Host $Firstpart -NoNewline;
Write-Host $TooShortDifference -ForegroundColor Red
Write-Host ''
Write-Host -ForegroundColor Green -NoNewline 'The answer is: '
Write-Host -ForegroundColor White $Frage
Write-Host ''
}
Elseif ($Antwort.Length -lt $Frage.Length) {
$TooLongDifference = $Frage[($Antwort.Length)..$Frage.Length]
Write-Host ''
Write-Host -ForegroundColor Red 'Your Answer has not enough letters'
Write-Host ''
Write-Host -ForegroundColor Green -NoNewline 'You wrote: '
Write-Host -ForegroundColor White $Antwort
Write-Host ''
Write-Host -ForegroundColor Green -NoNewline 'The answer is: '
Write-Host -ForegroundColor White $Antwort -NoNewline
Write-Host -ForegroundColor Red $TooLongDifference -Separator ''
}
Elseif ($Antwort.Length -eq $Frage.Length) {
$SameLengthWithError = $Frage[0..($Script:Counter - 1)]
Write-Host ''
Write-Host -ForegroundColor Red 'Your Answer has a letter mismatch'
Write-Host ''
Write-Host -ForegroundColor White 'You Wrote: ' -NoNewline
Write-Host $SameLengthWithError -NoNewline -Separator ''
Write-Host $Antwort[$Script:Counter] -ForegroundColor Red -NoNewline
Write-Host $Antwort[($Script:Counter + 1)..$Antwort.Length] -Separator ''
Write-Host ''
Write-Host -ForegroundColor Green 'The Answer is: ' -NoNewline
Write-Host -ForegroundColor Green $Frage
}
Else {
Write-Host -ForegroundColor Green 'Everything was correct'
}
If you would build something similar, which way would you use and how would you show multiple lines, where within the font colors would change on each line?

Powershell selection validation failing

I am having some issues with a dynamic picklist I am generating for a script. I can see it return the list of options and I can select each of them. However the validation I have is not working as I would have expected.
Write-Host "Gathering cluster information..." -foreground Green
$allclusters = Get-Cluster | Sort Name
$allHosts = $allclusters | Get-VMHost
Write-Host "Found " $allclusters.count " containing " $allHosts.count " Hosts." -Foreground Green
# LoopMain Start
Do {
$userMenuChoice = "y"
Write-Host "Select Cluster to patch:" -Foreground Yellow
for($i=0;$i -le $allclusters.length-1;$i++)
{"[{0}] - {1}" -f $i,$allclusters[$i]}
# Select VMCluster
Write-Host ""
Write-Host "Which Cluster would you like to use (0 to"($allclusters.length-1)")" -ForegroundColor Cyan -NoNewLine ; $clusterSelect = Read-Host " "
Write-Host ""
# Validate selection
IF ($clusterSelect -le ($allclusters.length-1))
{
Write-Host "Selection is valid"
# Display item from clusterarray
Write-Host ""
Write-Host "You selected " -NoNewLine ; Write-Host $allclusters[$clusterSelect] -ForegroundColor Cyan -NoNewLine ; $clusterSelectCont = Read-Host ". Shall we continue? (Y/N)"
}
ELSE
{
Write-Host "Selection is not valid"
$clusterSelectCont = "n"
}
The picklist will work for some number but not all. For example I can select number 12 form the list and everything is fine but number 7 fails. Is there something fundamentally wrong with the way I have created the picklist or is it the validation that is failing perhaps?
Ah, so discussed with a colleague here and the suggestion was to cast the variable to an integer using the following line:
[int]$clusterSelect = $clusterSelect
This now validates as expected and I can select all values without issue.

Pause on failure, retry input from CSV

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

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 ""
}
}