Powershell | Stuck in while loop - powershell

I have a small script that checks the communication date of the mcafee agent:
$GetLastCommunication = & "C:\Program Files\McAfee\Agent\cmdagent" -i | Select-String -Pattern LastASCTIME | Out-String
$GetLastCommunication = $GetLastCommunication.Replace("`n", "").Replace("`r", "").Replace(" ","")
Start-Job -ScriptBlock {
$newagentdir = (Get-ChildItem -Recurse "\\IP\Downloads\" -Include "FramePkg.exe").FullName
$arg = "/forceinstall /install=agent /silent"
Copy-Item -Path $newagentdir -Destination $env:TEMP
Start-Process -Wait $env:TEMP\FramePkg.exe -ArgumentList $arg
}
while ($GetLastCommunication = "LastASCTime:N/A"){
sleep -Seconds 5
}
Reload-Form
after the installation, the LastASCTime line changes automatically from "N/A" to the time of communication, after 15 to 20 seconds.
I need for him to reload the form when the LastASCTime line changes from N/A to the comm time.
Currently, is getting stuck in the while loop and doesn’t reload the form.
Any ideas why?
Thanks

Your code is essentially...
$lastKnownState = Get-SomeState
while ($lastKnownState -eq $operationInProgressState)
{
sleep -Seconds 5
}
Your loop is waiting 5 seconds between checking if the operation has completed, but because $lastKnownState is just a snapshot of the operation's state and not modified inside the loop, the loop will never terminate.
Instead, you need to actually refresh the state after the timeout...
$lastKnownState = Get-SomeState
while ($lastKnownState -eq $operationInProgressState)
{
sleep -Seconds 5
$lastKnownState = Get-SomeState
}
In your code, after correcting the = assignment operator to the -eq comparison operator, that would look like this...
while ($GetLastCommunication -eq "LastASCTime:N/A"){
sleep -Seconds 5
$GetLastCommunication = & "C:\Program Files\McAfee\Agent\cmdagent" -i | Select-String -Pattern LastASCTIME | Out-String
$GetLastCommunication = $GetLastCommunication.Replace("`n", "").Replace("`r", "").Replace(" ","")
}
Also, instead of chaining calls to Replace() with...
$GetLastCommunication = $GetLastCommunication.Replace("`n", "").Replace("`r", "").Replace(" ","")
...you can use the -replace operator with a regular expression that will match and remove CR, LF, or space characters...
$GetLastCommunication = $GetLastCommunication -replace '[\r\n ]'
Those characters are being escaped for the .NET regular expression engine and not PowerShell, which is why backslashes are used instead of backticks. If what you really want to do is match LF optionally preceded by a CR (i.e. CRLF or LF) that can be matched with \r?\n...
$GetLastCommunication = $GetLastCommunication -replace '\r?\n| '

Related

Start-Process with Process Priority in Loop doesn't recognize variable

I'm using FFmpeg with PowerShell.
I have a loop that goes through a folder of mpg files and grabs the names to a variable $inputName.
FFmpeg then converts each one to an mp4.
Works
Batch Processing
$files = Get-ChildItem "C:\Path\" -Filter *.mpg;
foreach ($f in $files) {
$inputName = $f.Name; #name + extension
$outputName = (Get-Item $inputName).Basename; #name only
ffmpeg -y -i "C:\Users\Matt\Videos\$inputName" -c:v libx264 -crf 25 "C:\Users\Matt\Videos\$outputName.mp4"
}
Not Working
Batch Processing with Process Priority
$files = Get-ChildItem "C:\Path\" -Filter *.mpg;
foreach ($f in $files) {
$inputName = $f.Name; #name + extension
$outputName = (Get-Item $inputName).Basename; #name only
($Process = Start-Process ffmpeg -NoNewWindow -ArgumentList '-y -i "C:\Users\Matt\Videos\$inputName" -c:v libx264 -crf 25 "C:\Users\Matt\Videos\$outputName.mp4"' -PassThru).PriorityClass = [System.Diagnostics.ProcessPriorityClass]::AboveNormal;
Wait-Process -Id $Process.id
}
If I set the Process Priority using Start-Process PriorityClass, the $inputName variable is no longer recognized.
Error:
C:\Users\Matt\Videos\$inputName: No such file or directory
Lets go over a few basic things.
In powershell we love piping |, It allows use to pass the information from one command to another command.
A good example of this is the ForEach you have.
Instead of Foreach($F in $Files) you can pipe | into a foreach-object
Get-ChildItem "C:\Path\" -Filter *.mpg | Foreach-Object{
$_
}
When Piping | a command powershell automatically creates the variable $_ which is the object that is passed in the pipe |
The next thing is there are 2 types of quotes " and '.
If you use ' then everthing is taken literally. Example
$FirstName = "TestName"
'Hey There $FirstName'
Will return
Hey There $FirstName
While " allows you to use Variables in it. Example
$FirstName = "TestName"
'Hey There $FirstName'
Will return
Hey There TestName
Now one last thing before we fix this. In powershell we have a escape char ` aka a tick. Its located beside the number 1 on the keyboard with the tilde. You use it to allow the use of char that would otherwise break out of the qoutes. Example
"`"Hey There`""
Would return
"Hey There"
OK so now that we covered the basics lets fix up the script
Get-ChildItem "C:\Users\Matt\Videos\" -Filter *.mpg -File | Foreach-Object{
($Process = Start-Process ffmpeg -NoNewWindow -ArgumentList "-y -i `"$($_.FullName)`" -c:v libx264 -crf 25 `"C:\Users\Matt\Videos\$($_.Name)`"" -PassThru).PriorityClass = [System.Diagnostics.ProcessPriorityClass]::AboveNormal;
Try{
Wait-Process -Id $Process.id
}catch{
}
}
In the case above I changed
Add -File to the Get-ChildItem to designate that you only want Files returned not folders
Pipe | into a Foreach-Object
Changed the Outside Brackets in the -ArgumentList to be double quotes " instead of literal quotes '
Removed the $InputName and $OutputName in favor of the Foreach-Object variable $_

PS1 Replace or Remove a single character without rewriting the whole file

Is it possible to remove or replace the last character on the last non-whitespace line of a file using PowerShell 1?
I'm trying to get an Uptime log that is precise to within 5 minutes.
I've found that there are built logs and commands that can be accessed through command prompt that would tell me when the last time a computer was booted up, or when it shut down correctly, but the native uptime log only records once every 24 hrs, so if there is a power failure, I won't know how long the system has been offline with any precision more refined than 24 hours.
So I have created the following script:
$bootTime = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
$formBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($bootTime)
$uptime = (Get-Date)-$formBootTime
"$formBootTime,$(Get-Date),{0:00},{1:00},{2:00},{3:00}" -f $uptime.Days,$uptime.Hours,$uptime.Minutes,$uptime.Seconds >> C:\UptimeTracker.csv
However, this gets tediously long to scroll through when I want to evaluate how long my machine has been running over the last X days.
So I thought I would add a marker to identify the current or most recent Uptime log per any given Boot.
But in order for that to work I would need to be able to remove said marker as soon as the previous record is no longer the relevant record.
$bootTime = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
$formBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($bootTime)
$file = (Get-Content c:\UptimeTracker.csv | Measure-Object)
$numberOfLines = $file.Count
$numberOfWords = (Get-Content c:\UptimeTracker.csv | Select -Index ($numberOfLines -1) | Measure-Object -word)
$Line = Get-Content c:\UptimeTracker.csv | Select -Index ($numberOfLines -2)
$wordArray = $Line.Split(",")
$LastLineBT = $wordArray[0]
if($LastLineBT -eq $formBootTime) {
$unmark = "true"
}
else
{$unmark = "false"}
if($unmark == "true"){ <remove last character of file> }
$uptime = (Get-Date)-$formBootTime
"$formBootTime,$(Get-Date),{0:00},{1:00},{2:00},{3:00},X" -f $uptime.Days,$uptime.Hours,$uptime.Minutes, $uptime.Seconds >> C:\UptimeTracker.csv
Some of the above is borrowed and modified from: https://stackoverflow.com/a/16210970/11035837
I have seen several methods that receive the file as the input file and write to a different output file, and from there it would be an easy thing to do to script renaming the new and old files to switch their positions (new, old, standby - and rotate) the reason I'm trying not to rewrite the whole file is to reduce those instances where the command/script is interrupted and the action doesn't complete. Ideally the only time the action doesn't complete would be on a power failure. However, I have already seen in a previous version, it would skip 5 minute intervals occasionally for up to 15 minutes without any change in the last reported boot time. I suspect this has to do with other higher priority processes preventing the task scheduler from running the script. If this is the case, then a complete rewrite of the file failing part way through the script would lose some percentage of the existing log data, and I would rather miss the latest record than all the data.
Nothing I have found indicates any ability to remove/replace the last character (or two since one is a newline char), neither have I found anything that explicitly declares this is not possible - I have found declarations that it is not possible to elective replace inner or beginning content without a complete rewrite.
Barring any solution definitive answer, or if the definitive answer is no this cannot be done, then I will attempt something like the following:
if($unmark == "true"){
$input = "C:\UptimeTracker_CUR.csv"
$output = "C:\UptimeTracker_NEW.csv"
$content = Get-Content $input
$content[-2] = $content[-2] -replace 'X', ' '
$content | Set-Content $output
Rename-Item -Path "C:\UptimeTracker_CUR.csv" -NewName "C:\UptimeTracker_SBY.csv"
Rename-Item -Path "C:\UptimeTracker_NEW.csv" -NewName "C:\UptimeTracker_CUR.csv"
}
EDIT - due to multi-read comment by TheMadTechnician
...
$file = Get-Content c:\UptimeTracker.csv
$fileMeasure = ($file | Measure-Object)
$numberOfLines = $fileMeasure.Count
$numberOfWords = ($file | Select -Index ($numberOfLines -1) | Measure-Object -word)
$Line = $file | Select -Index ($numberOfLines -2)
...
...
if($unmark == "true"){
$output = "C:\UptimeTracker_NEW.csv"
$file[-2] = $file[-2] -replace 'X', ' '
$file | Set-Content $output
Rename-Item -Path "C:\UptimeTracker.csv" -NewName "C:\UptimeTracker_SBY.csv"
Rename-Item -Path "C:\UptimeTracker_NEW.csv" -NewName "C:\UptimeTracker.csv"
}
You read the whole file in several times, which has got to be slowing the whole script down. I would suggest reading the whole file in, determining if you need to clear your flag, then do so when you output, adding your new line to the file. Assuming you aren't still running PowerShell v2, you can do this:
$bootTime = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
$formBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($bootTime)
$uptime = (Get-Date)-$formBootTime
$File = Get-Content c:\UptimeTracker.csv -raw
if($File.trim().split("`n")[-1].Split(',')[0] -eq $formBootTime){
$File.trim() -replace 'X(?=\s*$)',' '
}else{
$File.Trim()
},("$formBootTime,$(Get-Date),{0:00},{1:00},{2:00},{3:00},X" -f $uptime.Days,$uptime.Hours,$uptime.Minutes, $uptime.Seconds)|Set-Content c:\UptimeTracker.csv
If you are running an old version you will not have the -raw option for Get-Content. As a work around you can do this instead, and the same solution should still work.
$bootTime = (Get-WmiObject Win32_OperatingSystem).LastBootUpTime
$formBootTime = [Management.ManagementDateTimeConverter]::ToDateTime($bootTime)
$uptime = (Get-Date)-$formBootTime
$File = (Get-Content c:\UptimeTracker.csv) -Join "`n"
if($File.trim().split("`n")[-1].Split(',')[0] -eq $formBootTime){
$File.trim() -replace 'X(?=\s*$)',' '
}else{
$File.Trim()
},("$formBootTime,$(Get-Date),{0:00},{1:00},{2:00},{3:00},X" -f $uptime.Days,$uptime.Hours,$uptime.Minutes, $uptime.Seconds)|Set-Content c:\UptimeTracker.csv
This is going to be slower, so should be considered a secondary option since you'll have to read the whole file in as an array of strings, and convert it to a single multi-line string.

Overwrite PowerShell output strings onto the same line

I have a piece of PS code which takes the 7-Zip extraction output and filters it down so only percentage "%" progress update lines get printed. I've managed to reduce it down to just the percentage outputs:
& $7ZipPath "x" $filePath "-o$extractionPath" "-aos" "-bsp1" | out-string -stream | Select-String -Pattern "\d{1,3}%" -AllMatches | ForEach-Object { $_.Matches.Value } | Write-Host -NoNewLine
At the moment the console output looks like this:
0%1%5%9%14%17%20%23%26%31%37%43%46%48%50%52%54%56%59%61%63%65%67%70%72%74%76%78%80%81%82%83%85%86%87%89%90%91%92%94%95%96%97%98%99%
Is there a way of keeping these outputs in the same place, on the same line, making them just overwrite each other? It's tricky because the output is being piped from the 7-Zip application. I'm afraid I can't use Expand-Archive as I am dealing with .7z files
Many thanks!
You could use the .Net System.Console class:
[System.Console]::SetCursorPosition(0, [System.Console]::CursorTop)
So your code would have to be:
& $7ZipPath "x" $filePath "-o$extractionPath" "-aos" "-bsp1" | out-string -stream | Select-String -Pattern "\d{1,3}%" -AllMatches | ForEach-Object { $_.Matches.Value } | foreach {
[System.Console]::SetCursorPosition(0, [System.Console]::CursorTop)
Write-Host $_ -NoNewLine
}
Note: As long as the next output is equal or greater length, which is true in your case, this is all you need. Otherwise you would have to clear the last output first.
marsze's helpful answer works well, but there's a simpler alternative that uses a CR character ("`r") to reset the cursor position to the start of the line.
Here's a simple demonstration that prints the numbers 1 through 10 on the same line:
1..10 | ForEach-Object { Write-Host -NoNewline "`r$_"; Start-Sleep -Milliseconds 100 }
[Console]::Write(...) instead of Write-Host -NoNewline ... works too, as Bacon Bits points out.
The same constraint applies, however: if previous output lines happened to be longer, the extra characters linger.
To solve this problem too, you must pad any output line to the length of the console window's buffer width:
'loooooooong', 'meeedium', 'short' | ForEach-Object {
Write-Host -NoNewline ("`r{0,-$([console]::BufferWidth)}" -f $_)
Start-Sleep -Milliseconds 500
}

How to use Powershell Pipeline to Avoid Large Objects?

I'm using a custom function to essentially do a DIR command (recursive file listing) on an 8TB drive (thousands of files).
My first iteration was:
$results = $PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime
$results | Export-CVS -Path $csvfile -Force -Encoding UTF8 -NoTypeInformation -Delimiter "|"
This resulted in a HUGE $results variable and slowed the system down to a crawl by spiking the powershell process to use 99%-100% of the CPU as the processing went on.
I decided to use the power of the pipeline to WRITE to the CSV file directly (presumably freeing up the memory) instead of saving to an intermediate variable, and came up with this:
$PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime | ConvertTo-CSV -NoTypeInformation -Delimiter "|" | Out-File -FilePath $csvfile -Force -Encoding UTF8
This seemed to be working fine (the CSV file was growing..and CPU seemed to be stable) but then abruptly stopped when the CSV file size hit ~200MB, and the error to the console was "The pipeline has been stopped".
I'm not sure the CSV file size had anything to do with the error message, but I'm unable to process this large directory with either method! Any suggestions on how to allow this process to complete successfully?
Get-FolderItem runs robocopy to list the files and converts its output into a PSObject array. This is a slow operation, which isn't required for the actual task, strictly speaking. Pipelining also adds big overhead compared to the foreach statement. In the case of thousands or hundreds of thousands repetitions that becomes noticeable.
We can speed up the process beyond anything pipelining and standard PowerShell cmdlets can offer to write the info for 400,000 files on an SSD drive in 10 seconds.
.NET Framework 4 or newer (included since Win8, installable on Win7/XP) IO.DirectoryInfo's EnumerateFileSystemInfos to enumerate the files in a non-blocking pipeline-like fashion;
PowerShell 3 or newer as it's faster than PS2 overall;
foreach statement which doesn't need to create ScriptBlock context for each item thus it's much faster than ForEach cmdlet
IO.StreamWriter to write each file's info immediately in a non-blocking pipeline-like fashion;
\\?\ prefix trick to lift the 260 character path length restriction;
manual queuing of directories to process to get past "access denied" errors, which otherwise would stop naive IO.DirectoryInfo enumeration;
progress reporting.
function List-PathsInCsv([string[]]$PATHS, [string]$destination) {
$prefix = '\\?\' #' UNC prefix lifts 260 character path length restriction
$writer = [IO.StreamWriter]::new($destination, $false, [Text.Encoding]::UTF8, 1MB)
$writer.WriteLine('Name|Directory|Length|LastWriteTime')
$queue = [Collections.Generic.Queue[string]]($PATHS -replace '^', $prefix)
$numFiles = 0
while ($queue.Count) {
$dirInfo = [IO.DirectoryInfo]$queue.Dequeue()
try {
$dirEnumerator = $dirInfo.EnumerateFileSystemInfos()
} catch {
Write-Warning ("$_".replace($prefix, '') -replace '^.+?: "(.+?)"$', '$1')
continue
}
$dirName = $dirInfo.FullName.replace($prefix, '')
foreach ($entry in $dirEnumerator) {
if ($entry -is [IO.FileInfo]) {
$writer.WriteLine([string]::Join('|', #(
$entry.Name
$dirName
$entry.Length
$entry.LastWriteTime
)))
} else {
$queue.Enqueue($entry.FullName)
}
if (++$numFiles % 1000 -eq 0) {
Write-Progress -activity Digging -status "$numFiles files, $dirName"
}
}
}
$writer.Close()
Write-Progress -activity Digging -Completed
}
Usage:
List-PathsInCsv 'c:\windows', 'd:\foo\bar' 'r:\output.csv'
dont use robocopy, use native PowerShell command, like this :
$PATHS = 'c:\temp', 'c:\temp2'
$csvfile='c:\temp\listresult.csv'
$PATHS | % {Get-ChildItem $_ -file -recurse } | Select Name,DirectoryName,Length,LastWriteTime | export-csv $csvfile -Delimiter '|' -Encoding UTF8 -NoType
Short version for no purist :
$PATHS | % {gci $_ -file -rec } | Select Name,DirectoryName,Length,LastWriteTime | epcsv $csvfile -D '|' -E UTF8 -NoT

Error with foreachloop within foreachloop Powershell

I have written script with the purpose of filtering different jobs which I ripped from a server based on the '#command=N'
$orginal_submap = 'C:\Users\Desktop\Jobs_from_Server\Jobs_Orgineel_opdr_2.3'
$orginal_rejected = 'C:\Users\Desktop\Jobs_from_Server\Jobs_Orgineel_opdr_2.3\gefaald'
$fileserver = Get-ChildItem $orginal_submap *.sql
$stringfile = '#command=N''/FILE*'
$stringisserver = '#command=N''/ISSERVER*'
$commandline = '#command=N'
$startloop = 1
foreach ($fileser in $fileserver)
{
$currentline = Select-String $fileser -pattern $commandline
#countss how often the #command is containded in the file
$numberoftimesloopd = $currentline.length
do
{
if ($startloop -gt $numberoftimesloopd) {break}
foreach ($commandline in $currentline)
{
$startloop
if ($commandline -match $stringfile) {'#command=N''/FILE'}
elseif ($commandline -match $stringisserver) {'#command=N''/ISSERVER'}
else {'gefaald'}
#if the amount of loops is equel to the number of '#command=N' it stops the loop
$startloop++
if ($startloop -gt $numberoftimesloopd) {break}
}
} while ($startloop-le $numberoftimesloopd)
einde
}
My problem is that instead of quitting after running a job or simply gets the next *sql file from the source map it gives an error namely
It says that the error is in $currentline = Select-String $fileser -pattern $commandline
Problem is I have looked multiple times and try different things like adding start loop to stop the program to try keep running the script.
Can someone help solve the error and ensure that the script will quit if all files are done and if not take the next job from the source.
Or help me pinpoint the source of the problem/possible solution
In the following line you should use -SimpleMatch parameter so Select-String does not interpret the value of the Pattern parameter as a regular expression statement:
$currentline = Select-String $fileser -pattern $commandline -SimpleMatch