I'm working on a process to start up some services across remote servers, however server 2 can't start up until a message is found in server1 logs, server 3 can't start until same message in server 2, etc.
My question is, is it possible to read the file in a "loop" and not proceed with my initial loop until that message is found (and then move forward)? I was thinking I could do below, however while it does recognize that the string in the log file is found, it just repeats that it found it until the timer built in finishes and then moves forward.
So, the process would look like "read this file, if string is found, move forward. If string is not found, wait 30 seconds and rescan the file. If found, move forward, if not found, wait an additional 30 seconds and rescan (I'm fine with a continuous repeat) < do this until string is found.
Example: enter image description here
Any advice would be much appreciated as I think I might be approaching this from the wrong angle...
-- I left out the majority of the script prior to this script and only included the If/Else statement as this is where it checks the files.
$SEL = Select-String -Path \\$Server\$RootDir\folder\anotherfolder\A-Log-File.log -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
}
else
{
Write-Host **** Waiting 60 seconds for cache to build ****
[int]$Time = 60
$Lenght = $Time / 100
For ($Time; $Time -gt 0; $Time--) {
$min = [int](([string]($Time/60)).split('.')[0])
$text = " " + $min + " minutes " + ($Time % 60) + " seconds left"
Write-Progress -Activity "Waiting for Started Message" -Status $Text -PercentComplete ($Time / $Lenght)
Start-Sleep 1
$SEL = Select-String -Path \\$Server\$RootDir\folder\anotherfolder\A-Log-File.log -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
}
else
{
Write-Host **** A-Log-File.log Log does NOT contain a started message **** -ForegroundColor Red -BackgroundColor Yellow
Write-Host **** Investigate further or increase the int-time time on Line 54 to 180 seconds **** -ForegroundColor Red -BackgroundColor Yellow ##This part goes away once action can be taken based on reading contents of the file
}
}
}
You don't need a loop, just use Get-Content -Wait:
$null = Get-Content "\\$Server\$RootDir\folder\anotherfolder\A-Log-File.log" -Wait |Where-Object { $_ -match 'Switching to status: STARTED' } |Select -First 1
Get-Content -Wait will continue outputting new lines written to the file until it's interrupted - luckily we can use Select -First 1 to stop the pipeline once we observe the string
You said "loop" so why aren't you using a loop?
while ($true) {
# (re)try
$SEL = Select-String -Path "\\$Server\$RootDir\folder\anotherfolder\A-Log-File.log" -Pattern "Switching to status: STARTED"
if ($SEL -ne $null)
{
Write-Host "FOUND: Switching to status: STARTED" -ForegroundColor Yellow -BackgroundColor DarkGreen
# exit the loop
break;
}
# wait
Write-Host "**** Waiting 60 seconds for cache to build ****"
Start-Sleep 1
}
#marsze, I meant to come back to this and update. I ended up using your suggestion and also wanted to add in that I was able to build in a timer. I wanted to provide the example for anyone that stumbles upon this.
while ($true) {
# (re)try
$SEL = Select-String -Path "\\$Server\$SomeRoot\A.Folder\b.folder2\ThisFileToParse.log" -Pattern "Message Here"
if ($SEL -ne $null)
{
Write-Host "FOUND: Message Here" -ForegroundColor Yellow -BackgroundColor DarkGreen
# exit the loop
break;
}
# wait
Write-Host "**** File Does NOT contain a Message Here message ****" -ForegroundColor Red -BackgroundColor Yellow
Write-Host "**** Waiting 30 seconds, will loop until message is found ****" -ForegroundColor Red -BackgroundColor Yellow
#Start-Sleep 1
[int]$Time = 30
$Lenght = $Time / 100
For ($Time; $Time -gt 0; $Time--) {
$min = [int](([string]($Time/60)).split('.')[0])
$text = " " + $min + " minutes " + ($Time % 60) + " seconds left"
Write-Progress -Activity "Waiting for ThisFileToParse.log to update showing Message Here... " -Status $Text -PercentComplete ($Time / $Lenght)
Start-Sleep 1
}
Related
I have searched but apparently my google foo is weak. What I need is a way to prompt for user input in the console and have the request time out after a period of time and continue executing the script if no input comes in. As near as I can tell, Read-Host does not provide this functionality. Neither does $host.UI.PromptForChoice() nor does $host.UI.RawUI.ReadKey(). Thanks in advance for any pointers.
EDIT: Much thanks to Lars Truijens for finding the answer. I have taken the code that he pointed out and encapsulated it into a function. Note that the way that I have implemented it means there could be up to one second of delay between when the user hits a key and when script execution continues.
function Pause-Host
{
param(
$Delay = 1
)
$counter = 0;
While(!$host.UI.RawUI.KeyAvailable -and ($counter++ -lt $Delay))
{
[Threading.Thread]::Sleep(1000)
}
}
Found something here:
$counter = 0
while(!$Host.UI.RawUI.KeyAvailable -and ($counter++ -lt 600))
{
[Threading.Thread]::Sleep( 1000 )
}
It's quite old now but how I solved it based on the same KeyAvailable method is here:
https://gist.github.com/nathanchere/704920a4a43f06f4f0d2
It waits for x seconds, displaying a . for each second that elapses up to the maximum wait time. If a key is pressed it returns $true, otherwise $false.
Function TimedPrompt($prompt,$secondsToWait){
Write-Host -NoNewline $prompt
$secondsCounter = 0
$subCounter = 0
While ( (!$host.ui.rawui.KeyAvailable) -and ($count -lt $secondsToWait) ){
start-sleep -m 10
$subCounter = $subCounter + 10
if($subCounter -eq 1000)
{
$secondsCounter++
$subCounter = 0
Write-Host -NoNewline "."
}
If ($secondsCounter -eq $secondsToWait) {
Write-Host "`r`n"
return $false;
}
}
Write-Host "`r`n"
return $true;
}
And to use:
$val = TimedPrompt "Press key to cancel restore; will begin in 3 seconds" 3
Write-Host $val
For people who are looking for a modern age solution with an additional constraint for exiting a PowerShell script on a pre-defined key press, the following solution might help you:
Write-Host ("PowerShell Script to run a loop and exit on pressing 'q'!")
$count=0
$sleepTimer=500 #in milliseconds
$QuitKey=81 #Character code for 'q' key.
while($count -le 100)
{
if($host.UI.RawUI.KeyAvailable) {
$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp")
if($key.VirtualKeyCode -eq $QuitKey) {
#For Key Combination: eg., press 'LeftCtrl + q' to quit.
#Use condition: (($key.VirtualKeyCode -eq $Qkey) -and ($key.ControlKeyState -match "LeftCtrlPressed"))
Write-Host -ForegroundColor Yellow ("'q' is pressed! Stopping the script now.")
break
}
}
#Do your operations
$count++
Write-Host ("Count Incremented to - {0}" -f $count)
Write-Host ("Press 'q' to stop the script!")
Start-Sleep -m $sleepTimer
}
Write-Host -ForegroundColor Green ("The script has stopped.")
Sample script output:
Refer Microsoft document on key states for handling more combinations.
Credits: Technet Link
Here is a keystroke utility function that accepts:
Validation character set (as a 1-character regex).
Optional message
Optional timeout in seconds
Only matching keystrokes are reflected to the screen.
Usage:
$key = GetKeyPress '[ynq]' "Run step X ([y]/n/q)?" 5
if ($key -eq $null)
{
Write-Host "No key was pressed.";
}
else
{
Write-Host "The key was '$($key)'."
}
Implementation:
Function GetKeyPress([string]$regexPattern='[ynq]', [string]$message=$null, [int]$timeOutSeconds=0)
{
$key = $null
$Host.UI.RawUI.FlushInputBuffer()
if (![string]::IsNullOrEmpty($message))
{
Write-Host -NoNewLine $message
}
$counter = $timeOutSeconds * 1000 / 250
while($key -eq $null -and ($timeOutSeconds -eq 0 -or $counter-- -gt 0))
{
if (($timeOutSeconds -eq 0) -or $Host.UI.RawUI.KeyAvailable)
{
$key_ = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,IncludeKeyUp")
if ($key_.KeyDown -and $key_.Character -match $regexPattern)
{
$key = $key_
}
}
else
{
Start-Sleep -m 250 # Milliseconds
}
}
if (-not ($key -eq $null))
{
Write-Host -NoNewLine "$($key.Character)"
}
if (![string]::IsNullOrEmpty($message))
{
Write-Host "" # newline
}
return $(if ($key -eq $null) {$null} else {$key.Character})
}
timeout.exe 5
still works from powershell. Waits 5 seconds or until key press.
Inelegant perhaps. But easy.
However,
From the Powershell_ISE it pops up a new command prompt window, and returns immediately, so it doesnt wait (From the powershell console it uses that console and does wait). You can make it wait from the ISE with a little more work (still pops up its own window tho):
if ($psISE) {
start -Wait timeout.exe 5
} else {
timeout.exe 5
}
Another option: you can use choice shell command which comes with every Windows version since Windows 2000
Choice /C yn /D n /t 5 /m "Are you sure? You have 5 seconds to decide"
if ($LASTEXITCODE -eq "1") # 1 for "yes" 2 for "no"
{
# do stuff
}
else
{
# don't do stuff
}
Stackoverflow syntax higlighting doesn't work for powershell, # means "comment" here
function ReadKeyWithDefault($prompt, $defaultKey, [int]$timeoutInSecond = 5 ) {
$counter = $timeoutInSecond * 10
do{
$remainingSeconds = [math]::floor($counter / 10)
Write-Host "`r$prompt (default $defaultKey in $remainingSeconds seconds): " -NoNewline
if($Host.UI.RawUI.KeyAvailable){
$key = $host.UI.RawUI.ReadKey("IncludeKeyUp")
Write-Host
return $key
}
Start-Sleep -Milliseconds 100
}while($counter-- -gt 0)
Write-Host $defaultKey
return $defaultKey
}
$readKey = ReadKeyWithDefault "If error auto exit( y/n )" 'y' 5
To optionally pause the script before it exits (useful for running headless scripts and pausing on error output), I appended nathanchere's answer with:
if ([Console]::KeyAvailable) { $pressedKey = [Console]::ReadKey($true); read-host; break; }
elseif ($secondsCounter -gt $secondsToWait) {
Write-Host "`r`n"
return $false;
}
The $Host.UI.RawUI.KeyAvailable seems to be buggy so I had more luck using [Console]::KeyAvailable:
function keypress_wait {
param (
[int]$seconds = 10
)
$loops = $seconds*10
Write-Host "Press any key within $seconds seconds to continue"
for ($i = 0; $i -le $loops; $i++){
if ([Console]::KeyAvailable) { break; }
Start-Sleep -Milliseconds 100
}
if ([Console]::KeyAvailable) { return [Console]::ReadKey($true); }
else { return $null ;}
}
I have a PowerShell script for Microsoft Teams that looks at a list of Users in a .csv file and returns a few properties of the user, then exports it to another .csv with the results. This works fine except the progress bar is stuck displaying the last entry until I hit enter. How do you make the last entry disappear and only show "Done Executing Script" and return to C:>
Im trying to add the time at the end of the line when each of the lines from the CSV is executed, Please help me with that. Example below:
Processing User Number: 1 [Currently Processing: User1#abc.com] - Policies applied in "5s"
Processing User Number: 2 [Currently Processing: User2#abc.com] - Policies applied in "2s"
Processing User Number: 3 [Currently Processing: User3#abc.com] - Policies applied in "10s"
Processing User Number: 4 [Currently Processing: User4#abc.com] - Policies applied in "8s"
Processing User Number: 5 [Currently Processing: User5#abc.com] - Policies applied in "25s"
CODE FOR POWERSHELL SCRIPT:
Write-host "Connecting to Microsoft Teams....." -ForegroundColor Yellow
Connect-MicrosoftTeams
Write-host "Successfully connected to Microsoft Teams" -ForegroundColor Green
$CsvFilePath = Import-CSV -Path "C:\Users\\Desktop\MicrosoftTeams\precheckbatch2.csv"
$Count=0
$results = foreach ($UPN in $CsvFilePath) {
$user = $UPN.UserPrincipalName
Get-CsOnlineUser $user | Select-Object Displ*, UserPri*, IsSipEnabled, TeamsUpgradeE*,
Enterprise*, #{l="FeatureTypes";e={$_.FeatureTypes -join "; "}}, #{l="AssignedPlan";e=
{$_.AssignedPlan -join "; "}}
Write-Progress -Activity "Processing User: $Count" "Currently Processing: $user"
$Count++
}
$results | Export-Csv C:\Users\\Desktop\MicrosoftTeams\precheckdataresults1.csv
Write-host "Done!"
This seems to be an issue of VSCode only. When I run this simplified script in the console, the progress bar disappears automatically, when the script ends. When I run it from VSCode, the progress bar stays visible, reproducing the issue.
foreach( $count in 1..3 ) {
$user = "User$count"
Write-Progress -Activity "Processing User: $Count" "Currently Processing: $user"
Start-Sleep 1
}
To fix the problem for VSCode, add a Write-Progress -Completed line, to explicitly remove the progress bar:
foreach( $count in 1..3 ) {
$user = "User$count"
Write-Progress -Activity "Processing User: $Count" "Currently Processing: $user"
Start-Sleep 1
}
Write-Progress -Activity "Processing User: $Count" -Completed
I'm working on a powershell script that would allow me to see if my DHCP servers are running well and what's their current state (how much address left, how much of them are used...).
So far, so good. I'm getting all the info i need but, the first test here is to check if the server is responding pretty much.
For this, i'm using try-catch and i'd like to output into a .txt all of the servers that are not responding.
Problem : i'm only outputting the last server that did not respond, i'm not getting the previous one that did not respond aswell.
Tried | out-file
| set-content and | add-content
There's nothing that i've found searching seems to work.
$DHCPSRV=""
$myError=0
$myArray=#( Import-Csv .\CSV\DHCP_list.csv)
foreach ($element in $myArray) {
try {
Write-Output ""
$DHCPSRV=$element.FQDN
$Message = "Server DHCP: " + $DHCPSRV
Write-Output $Message
Write-Output ""
$srv=get-dhcpserverv4statistics -ComputerName $DHCPSRV
$Message ="Server start time : " + $srv.ServerStartTime
Write-Output $Message
$Message ="Number of address : " + $srv.TotalAddresses
Write-Output $Message
$Message ="Address used : " + $srv.AddressesInUse
Write-Output $Message
$Message ="% remaining : " + $srv.PercentageAvailable + " %"
Write-Output $Message
Get-DhcpServerSetting -ComputerName $DHCPSRV
Write-Output ""
}
catch{
Write-host "Server not responding " $DHCPSRV -BackgroundColor red -ForegroundColor White
$myerror=$error+1
$test = $DHCPSRV
}
}
if ($myError -eq 0){
Write-Output ""
Write-host "All DHCP are working good" -BackgroundColor green -ForegroundColor black
}
$test | Set-Content '.\Output\dhcp_failed.txt'
$test | Add-Content '.\Output\dhcp_failed.txt'
Write-Output ""
Write-Output ""
Write-Output "------------------------------------------------"
pause
I'd like to output all of the server that failed the try-catch test in my txt!
solved by bluuf - Thank you !
Just had to add the -append in my catch
catch{
Write-host "Server not responding " $DHCPSRV -BackgroundColor red -ForegroundColor White
$myerror=$error+1
$DHCPSRV | out-file '.\path\file.txt' -append
}
also added clear-content '.\path\file.txt' at the beginning of the script so my file get cleared every time i launch it!
$startTime = $(get-date)
write-host "`rElapsed:00:00:00"
$NoEvent = $true
While ($NoEvent)
{
Start-Sleep 1
$elapsedTime = new-timespan $startTime $(get-date)
write-host "`rElapsed:$($elapsedTime.ToString('hh\:mm\:ss'))"
#Handle event
if(event){$NoEvent = $false}
}
I've tried running this in the ISE as well as through the regular console. The returns are never output.
I eventually got it working using -NoNewLine switch
write-host -NoNewLine "`rElapsed:$($elapsedTime.ToString('hh\:mm\:ss'))"
`r only issues a CR, not a CR+LF (which is probably what you want). Use `n (‘newline’) instead.
I don't think there is any PS cmdlet that can help with overwriting text from the same line unless you are clearing the entire window with clear-host or cls, but PowerShell has a built in write-progress cmdlet if that is something else you would want to consider.
You can try:
$startTime = $(get-date)
$NoEvent = $true
While ($NoEvent)
{
for ($a=1; $a -lt 100; $a++) {
Start-Sleep 1
$elapsedTime = new-timespan $startTime $(get-date)
Write-Progress -Activity "`rElapsed:$($elapsedTime.ToString('hh\:mm\:ss'))" -PercentComplete $a -CurrentOperation "$a% Processed" -Status "Please wait."
#Handle event
if(event){$NoEvent = $false}
}
}
See: https://learn.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Utility/Write-Progress?view=powershell-5.1 for info on write-progress
and here is a related question: PowerShell - Overwriting line written with Write-Host
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.