Build an interactive menu using powershell - powershell

Below script will display the menu and prompt for a selection.However this doesn't give the option to reselect if the selection is made wrong.
I would like to re write in such a way that , it should give the option to confirm the selection and if the selection is made wrong, should give an option to re select from the menu. Is it possible using "do until" loop? Any help is high appreciated
write-host ""
Write-host -ForegroundColor yellow "Choose which Cluster you want to gather ratios on:"
write-host "(it may take a few seconds to build the list)"
write-host ""
$ICLUSTER = get-cluster -server $VIServer | Select-Object Name | Sort-object Name
if ($null -eq $ICLUSTER)
{
Update-log "Unable to find Cluster Information.Please verify the cluster status before proceed `n"
break
}
else
{
$i = 1
$ICLUSTER | %{Write-Host $i":" $_.Name; $i++}
$HCLUSTER = Read-host "Choose the name of cluster you want to select on by entering corresponding number:"
$SCLUSTER = $ICLUSTER[$HCLUSTER -1].Name
Update-log "You have selected $($SCLUSTER). `n"
start-sleep -s 3
}

A loop & couple additional if statements should get you there:
# break the loop by setting $x not equal to 1
$x = 1
While ($x -eq 1){
$ICLUSTER = get-cluster -server $VIServer | Select-Object Name | Sort-object Name
if ($null -eq $ICLUSTER){
Update-log "Unable to find Cluster Information. Please verify the cluster status before proceed `n"
$x = 2
}
else{
$i = 1
$ICLUSTER | %{Write-Host $i":" $_.Name; $i++}
$HCLUSTER = Read-host "Choose the name of cluster you want to select on by entering corresponding number:"
$SCLUSTER = $ICLUSTER[$HCLUSTER -1].Name
if ($SCLUSTER -in $ICLUSTER.Name){
Write-Host "You have selected $($SCLUSTER), do you want to continue?"
$yesno = Read-host "Please type 'Y' to continue or 'N' to quit"
if ($yesno -eq "Y"){
$x = 1
}
else {Write-Host "Quitting"
$x = 2
}
Write-Host "Performing commands"
# Add commands here, to do the work & complete the task
$x = 2
}
elseif (-not($SCLUSTER -in $ICLUSTER.Name)){
Write-Host "Your selection was not found, Would you like to try again?"
$yesno = Read-host "Please type 'Y' to try again or 'N' to quit"
if ($yesno -eq "Y"){
Write-Host "Trying this again"
$x = 1
}
else {Write-Host "Quitting"
$x = 2
}
}
start-sleep -s 3
}
}

Related

Increase Substring length dynamically

I am new to the community and need some help in Powershell.
I want the following code to dynamically check if the specific $sAMAccountName exists in an array or not.
If it exists, keep increasing the substring length by 1 and check the array again.
Instead of defining more variables like $sAMAccountName1 and $sAMaccountName2 (and so on) manualy.
$Nachname = "Nachname"
$Vorname = "Vorname"
$sAMAccountName = $Nachname+$Vorname.Substring(0,1)
$sAMAccountName1 = $Nachname+$Vorname.Substring(0,2)
$sAMAccountName2 = $Nachname+$Vorname.Substring(0,3)
$Array = #('NachnameV','NachnameVo','NachnameVor')
if($sAMAccountName -notin $Array){
Write-Host "$sAMAccountname does not exist :-)"
}
elseif($sAMAccountName1 -notin $Array){
Write-Host "$sAMAccountName1 does not exist :-)"
}
elseif($sAMAccountName2 -notin $Array){
Write-Host "$sAMAccountName2 does not exist :-)"
}
else{
Write-Host "$sAMAccountName2 does exist :-("
}
Thank you for your help.
Greetings,
PalimPalim
This may do what you need :
$Nachname = "Nachname"
$Vorname = "Vorname"
#$sAMAccountName = $Nachname+$Vorname
$Array = #('NachnameV','NachnameVo','NachnameVor')
$i = 0
do {
$i++
$sAMAccountName = $Nachname+$Vorname.Substring(0,$i)
write-host "i = $i"
write-host "account name = $sAMAccountName"
if($sAMAccountName -in $Array){
Write-Host "$sAMAccountName found"
}
} until ($sAMAccountName -notin $array)
Write-Host "$($Nachname+$Vorname.Substring(0,$i)) not found"
Where $i is used as an index which increases by 1 within the do until loop until the name being searched for no longer shows up in the array.
The write-host lines for i=$i and the account name aren't needed, but just let you see what the script is doing on each iteration round the loop, and can be removed.

Powershell error with else statement

I am trying to write a script that will remove old queues from users HKLM (will eventually delete from HKCU by mounting ntuser.dat but I am not there yet).
The problem I am having is that I am only iterating through one sid under SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Providers\Client Side Rendering Print Provider\ and I get the following error message:
The term 'else' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is corr
ect and try again.
Has anyone ran into this issue before?
#defining my object that will be used throughout the script. Will be used to log everything
$objQueueData=[pscustomobject]#{
computername=""
computerstatus=""
Registrystatus=""
SID=""
Does_It_Have_2003_Queues=""
User_SID_Status=""
user=""
UNC_2003_Queues=""
}
#$QueueDataCollection=[pscustomobject]#{
#queuecollection=$QueueData
#}
#reading the list of workstations
Get-Content "P:\PowerShell\jm\DeletePrintQueues\Workstations.txt" | ForEach-Object{
$strComputerName = $_
#check if the workstation is up
IF (Test-Connection -ComputerName $strComputerName -count 2 -quiet)
{
#$objUser= Get-ChildItem c:\users
#$strUserName=$objUser.Name
$objQueueData.computername=$strComputerName
$objQueueData.computerstatus="Machine is up"
DeleteHklm $strComputerName
}
else
{
#We are here because the computer could not be reached
Write-Host "Machine down" $strComputerName
$objQueueData.computername =$strComputerName
$objQueueData.computerstatus = "Machine Down"
$objQueueData.Registrystatus ="Machine Down"
$objQueueData.SID = "Machine Down"
$objQueueData.Does_It_Have_2003_Queues="Machine Down"
$objQueueData.User_SID_Status="Machine Down"
$objQueueData.user="Machine Down"
$objQueueData.UNC_2003_Queues="Machine Down"
$objQueueData | Export-Csv P:\powershell\jm\results2.csv -NoTypeInformation -Append
}
}
function DeleteHKLM {
param ([string]$computername)
try{
If($strHklm = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine',$strcomputername ))
{
#executes when it can open HKLM
$objqueuedata.RegistryStatus = "Was able to open the registry"
#set the path of the registry
$PrinterRegKey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Providers\\Client Side Rendering Print Provider'
#$PrinterRegKey
$regPrinterRef = $strHklm.OpenSubKey($PrinterRegKey)
#debug
Write-Host "regprinterref is: "$regPrinterRef
}
If($regPrinterRef)
{
#This executes if there are Printers present in the registry
#region Loop thru all child keys. These contain the calculable UNC paths to 2003
$regPrinterRef.GetSubKeyNames() | ForEach-Object{
#debug
Write-Host "The sid is: " $_
#concatinating to get to the connections key
#$PrinterRegKey
$strPrinterpath =$PrinterRegKey+"\\"+ $_ + "\\Printers\\Connections"
#debug
Write-Host "The printer keys for SID are located in: "
$strPrinterPath
if ($strPrinterpath -notlike "*servers*")
{
#this value is the sid
# $_ will give us the sids. Here I am storing the SIDs into strUserSID to use later on
$strUserSID = $_
#debug
# $strUserSID
# The logic below will convert SID to username
#pass the SID to the secrity principal SID being struserSID
$objSID = New-Object System.Security.Principal.SecurityIdentifier("$strUserSID")
#using a try catch to filter out deleted SIDs, otherwise powershell will throw an error saying it is null
Try{
$strUser = $objSID.Translate( [System.Security.Principal.NTAccount]).Value
$objQueueData.User_SID_Status ="Valid SID"
$strUser
}
Catch{
#$strUserID = $objSID.Value
$objQueueData.User_SID_Status ="Invalid SID"
$objQueueData.User = "Invalid SID"
$objQueueData.Does_it_Have_2003_Queues ="Invalid SID"
$objQueueData.UNC_2003_Queues = "Invalid SID"
$objQueueData | Export-Csv P:\powershell\jm\results1.csv -NoTypeInformation -Append
#exit
}
$regPrinterDetails = $Strhklm.OpenSubKey($strPrinterPath)
$regPrinterDetails.GetSubKeyNames() |ForEach-Object{
#looping through each key at the connections level to search for the 2003 print server names
if($_ -like "*sarspprt2*")
{
$objQueueData.Does_It_Have_2003_Queues = "Yes"
#this value is the printer if it exists
# $_ will give us the printers. Here I am storing the printers into strUserPrinters to user later on
$strUserPrinters = $_
Write-Host "struserprinters value is " $_
#$strUserPrinters
$blnHasOldQueues = $true
#The code below is to build the printer UNC to make it more legible
$intPrinterLength= $strUserPrinters.Length
$strPrintServer= $strUserPrinters.Substring(2,10)
#Doing the -13 because we have to limit the length of the substring statement to the length minus the starting poistion of the substring
$strPrinterShareName =$strUserPrinters.Substring(13,$intPrinterLength-13)
$strPrintUNC = "\\"+$strPrintServer+"\"+$strPrinterShareName
$objQueueData.UNC_2003_Queues = $strPrintUNC
$objQueueData.User = $strUser
$objQueueData | Export-Csv P:\powershell\jm\results.csv -NoTypeInformation -Append
$strkeytodelete=$strPrinterPath+"\\"+$_
$strkeytodelete
#delete 2003 Key
Remove-Item -Path '$strkeytodelete' -Recurse
}
elseif($_ -notlike "*sarspprt2*")
{
#Write-host "No 2003 Queues Detected"
#Write-host "no 2003 Queues detected" $strUserSID,$strUser,$strPrintUNC
$objQueueData.User = $strUser
$objQueueData.Does_it_Have_2003_Queues = "No 2003 Queues Detected for this user or vsarspprt* queue"
$objQueueData.UNC_2003_Queues = "No 2003 Queues Detected for this user or vsarspprt* queue"
$objQueueData | Export-Csv P:\powershell\jm\results.csv -NoTypeInformation -Append
}
# Write-Host $strServer $blnHasOldQueues $strUserSID $strUserPrinters
}
}
}
else
{
#Write-Host "No Printers in the Registry"
$objQueueData.computername=""
$objQueueData.computerstatus=""
$objQueueData.Registrystatus=""
$objQueueData.SID=""
$objQueueData.Does_It_Have_2003_Queues=""
$objQueueData.User_SID_Status=""
$objQueueData.user=""
$objQueueData.UNC_2003_Queues=""
}
}
}
catch{
# Write-Host "cant read registry"
$_.Exception.Message
}
}
You have an extra curly brace on line 153. If you move that to after line 165 it should work, although I can't test it right now. I got in the habit of systematically collapsing my if-else statements to ensure that they all match up with eachother.

Is it possible to output subsequent information to same line

I have a script that pings an ip address and send that information to a console window. In the case of high ping times or missed pings, it is also written to a log. I would like to keep only the high ping times and missed pings in the console window and allow the good pings to overwrite each other. Is that possible?
For high ping times, this is the output (similar code is used for missed pings).
$out = ("{0}ms at $(get-date -format G)" -f $ping.ResponseTime)
write-host $out -foregroundcolor "yellow"
$out >> .\runningPing$ipAddress.txt
For normal ping times, the output is this.
$out ("{0}ms" -f $ping.ResponseTime)
write-host $out -foregroundcolor "green"
I'd like to make that last line just overwrite itself for normal pings, but let the high and missed pings push up the screen as the program runs. Is that something I can do in PS?
SOLUTION
Thanks to #Mathias R. Jensen, I came up with this solution:
if ($ping.statuscode -eq 0) {
if ($ping.responsetime -gt $waitTime) {
$highPings = $highPings + 1
$out = ("{0}ms at $(get-date -format G)" -f $ping.ResponseTime)
[console]::SetCursorPosition(0,$highPings + $droppedPings + 1)
write-host $out -foregroundcolor "yellow"
$out >> $outFile
}
else {
$out = ("{0}ms $i of $pingCount" -f $ping.ResponseTime)
[console]::SetCursorPosition(0,$highPings + $droppedPings + 2)
write-host $out -foregroundcolor "green"
}
}
else {
$droppedPings = $droppedPings + 1
$out = ("missed ping at $(get-date -format G)")
[console]::SetCursorPosition(0,$highPings + $droppedPings + 1)
write-host $out -foregroundcolor "red"
$out >> $outFile
}
I think you should use Write-Progress for the good pings. You don't need to give a percentage, and you can use the -Status parameter to show just the last good one.
Here's a small example I wrote that might demonstrate how it would look/operate (you can execute this yourself to see, it doesn't ping anything it's just a simulation):
$goods = 0
0..100 | % {
if ((Get-Random -Minimum 0 -Maximum 100) -ge 50) {
$goods += 1
Write-Progress -Activity Test -Status "Last good ping: $_ ($goods total good pings)"
} else {
Write-Warning "Bad ping"
}
Start-Sleep -Seconds 1
}
In this case you could have even calculated, for example a percentage of good pings and used that in Write-Progress but I wanted to show that you don't need to use it as a progress bar for it to be useful.
As I mentioned in the comments, the cursor position can be controlled by this method:
[control]::SetCursorPosition([int]$x,[int]$y)
The [console] type accelerator points to the same Console class that enables you to WriteLine() to the console in a C# console application. You can also control colors and other console behavior if you feel like it:
Clear-Host
[console]::ForegroundColor = "Red"
1..10|%{
[console]::SetCursorPosition(2+$_,$_-1)
[console]::WriteLine("Hello World!")
}
[console]::ForegroundColor = "Green"
briantist has the better approach to this but I was playing around and came up with this as well. It will not work in ISE but should do it's job on the PowerShell console. It uses "`b" which is the backspace character so that the text will overwrite itself on the console host write. Might not help you but could be useful to others.
switch($ping.ResponseTime){
{$_ -ge 0 -and $_ -le 100}{
$out = "{0}ms" -f $_
$options = #{ForegroundColor = "Green"; NoNewline = $true}
$backup = "`b" * $out.Length
}
{$_ -ge 500 -and $_ -le 900}{
$out = "{0}ms at $(get-date -format G)" -f $_
$options = #{ForegroundColor = "Yellow"; NoNewline = $false}
$backup = "`n"
}
}
Write-Host "$backup$out" #options
Uses a switch to set the options based on the the range of ping times. Sets a small hash table which is splatted to the write-host. Not perfect but it shows another way to do it.
Again this mostly done for fun.

Dynamic input array Powershell

once again I contact you because I am once again having problems with a Powershell script assignment.
My current assignement is to make a script that lets a user put in 1-10 values. The values have to be positive numbers. After that I have to calculate an average of an array.
My idea in this was the following:
clear-host
$averageArray = #()
Write-Host "How many numbers do you want to put in?" -for yellow
Write-Warning "Maximum is 10!"
Write-Host "Press the Enter key to continue."
$amountValues = Read-host
while($amountVAlues -notmatch "^([1-9]|[1][0])$") {
$amountValues = Read-Host "Please enter a number between 1 and 10"
if($amountVAlues -notmatch "^([1-9]|[1][0])$") {Write-host "Error! Please enter a number between 1 and 10!"}
}
$value1 = read-host "Enter number one"
while($value1 -notmatch '^\d+$') {
$value1 = Read-Host
if($value1 -notmatch '^\d+$') {Write-host "Error! Please enter a positive number!"}
}
$value2 = read-host "Enter number two"
while($value2 -notmatch '^\d+$') {
$value2 = Read-Host
if($value2 -notmatch '^\d+$') {Write-host "Error! Please enter a positive number!"}
}
$averageArray = $averageArray + $value1
write-host "$averageArray"
read-host
I created an array, and made the user input a number between 1-10 for the amount of total $values they want in the array. After that I wanted to loop the input of a $value, and input it in the array. I wanted to loop it as many times as the $amountValues variable.
Problem with doing this is that if I would loop it, $variable1 would get overwritten an 'x' amount of times.
Is there any way to input the values via a loop into the array?
I'ld do it like this:
while ( (1..10) -notcontains $g)
{
$g = read-host "How many numbers do you want to put in? (value from 1 to 10) "
}
$ar=#()
for ($i=0; $i -lt $g; $i++)
{
$ar += read-host "Enter value $($i+1)"
}
$averageArray = ($ar | Measure-Object -Average).average
write-host "Average is : $averageArray"

Hash table Get_Item returning blank line - Powershell

$searchterm = read-host “Enter search term for uninstallers”
$uninstallers = get-childitem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
$founditems = $uninstallers | ? {(Get-ItemProperty -path (“HKLM:\”+$_.name) -name Displayname -erroraction silentlycontinue) -match $searchterm}
write-host “Searched registry for uninstall information on $searchterm”
write-host “——————————————”
$x = 0
$uninstallcommandtable = #{}
$uninstalldisplaytable = #{}
if ($founditems -eq $null) {“None found”} else {
write-host “Found “($founditems | measure-object).count” item(s):`n”
$founditems | % {
$x = $x + 1
Write-host "Item: $x"
Write-host “Displayname: “$_.getvalue(“Displayname”)
Write-host “Displayversion: “$_.getvalue(“Displayversion”)
Write-host “InstallDate: “$_.getvalue(“InstallDate”)
Write-host “InstallSource: “$_.getvalue(“InstallSource”)
Write-host “UninstallString: “$_.getvalue(“UninstallString”)
$uninstallcommandtable.Add($x, $_.getvalue(“UninstallString”))
$uninstalldisplaytable.Add($x, $_.getvalue(“Displayname”))
Write-host “`n”
}
}
Write-host ($uninstalldisplaytable | Out-String)
$whichprogram = read-host "Which program do you want to uninstall?"
Write-host ($uninstallcommandtable.Get_Item($whichprogram) | Out-String)
For some reason the last Write-host is returning a blank line. I verified with a test output just before the last read-host, so I know the $uninstallcommandtable is proper. Any ideas would be great.
Because your hashtable Names are type System.Int32. This will show you that:
$uninstallcommandtable.Keys | % {$_.GetType().FullName}
Read-Host is setting a variable of type System.String. So you will need to convert the string to an System.Int32 like this:
Write-host $uninstallcommandtable.Get_Item([Int32] $whichprogram)
You can also use:
Write-host $uninstallcommandtable.Item([Int32] $whichprogram)
Alternatively, you can make the key a string when you create the hash entry:
$uninstallcommandtable.Add("$x", $_.getvalue(“UninstallString”))