Compare count of characters with a constant value, then execute next statement - powershell

I have a PowerShell script where I get the count of certain string from a file, then go to next step or execute next step only if the string count is 30.
I do have the code for getting the count of the string, as well the code for executing the next step. The only thing missing is to incorporate if statement.
To get the count of string, I am using the following:
$FileContent = Get-Content "YourFile.txt"
$Matches = Select-String -InputObject $FileContent -Pattern "/export" -AllMatches
To do the next step, I am using following;
"d:/scripts/plink.exe" -ssh %1 -l hpov -pw NDIA123 -m %com%|find "host" >>%lnm%
How to include if condition in between above two codes, so that last command will follow, only if the count of the string is more than 30?

First off, don't use $Matches for anything user-defined - it's an automatic variable.
With that out of the way: since Select-String may return multiple lines, you should sort them by length and test the length of the longest one:
$FileContent = Get-Content "YourFile.txt"
$LongestMatch = Select-String -InputObject $FileContent -Pattern "/export" -AllMatches |Sort-Object {$_.Line.Length} |Select-Object -Last 1
if($LongestMatch.Line.Length -gt 30){
# We found a match in a string longer than 30 chars!
# run plink here!
}

This may be too simplistic. I feel like there is something I must have missed in the question.
$FileContent = Get-Content "YourFile.txt"
$Matches = Select-String -InputObject $FileContent -Pattern "/export" -AllMatches
if ($Matches -eq 30) {
& "d:/scripts/plink.exe" -ssh %1 -l hpov -pw NDIA123 -m %com%|find "host" >>%lnm%
}

Related

How to add counter into Powershells ForEach-Object function

So I have a Pipe that will search a file for a specific stream and if found will replace it with a masked value, I am trying to have a counter for all of the times the oldValue is replaced with the newValue. It doesn't necessarily need to be a one liner just curious how you guys would go about this. TIA!
Get-Content -Path $filePath |
ForEach-Object {
$_ -replace "$oldValue", "$newValue"
} |
Set-Content $filePath
I suggest:
Reading the entire input file as a single string with Get-Content's -Raw switch.
Using -replace / [regex]::Replace() with a script block to determine the substitution text, which allows you to increment a counter variable every time a replacement is made.
Note: Since you're replacing the input file with the results, be sure to make a backup copy first, to be safe.
In PowerShell (Core) 7+, the -replace operator now directly accepts a script block that allows you to determine the substitution text dynamically:
$count = 0
(Get-Content -Raw $filePath) -replace $oldValue, { $newValue; ++$count } |
Set-Content -NoNewLine $filePath
$count now contains the number of replacements, across all lines (including multiple matches on the same line), that were performed.
In Windows PowerShell, direct use of the underlying .NET API, [regex]::Replace(), is required:
$count = 0
[regex]::Replace(
'' + (Get-Content -Raw $filePath),
$oldValue,
{ $newValue; ++(Get-Variable count).Value }
) | Set-Content -NoNewLine $filePath
Note:
'' + ensures that the call succeeds even if file $filePath has no content at all; without it, [regex]::Replace() would complain about the argument being null.
++(Get-Variable count).Value must be used in order to increment the $count variable in the caller's scope (Get-Variable can retrieve variables defined in ancestral scopes; -Scope 1 is implied here, thanks to PowerShell's dynamic scoping). Unlike with -replace in PowerShell 7+, the script block runs in a child scope.
As an aside:
For this use case, the only reason a script block is used is so that the counter variable can be incremented - the substitution text itself is static. See this answer for an example where the substitution text truly needs to be determined dynamically, by deriving it from the match at hand, as passed to the script block.
Changing my answer due to more clarifications in comments. The best way I can think of is to get the count of the $Oldvalue ahead of time. Then replace!
$content = Get-Content -Path $filePath
$toBeReplaced = Select-String -InputObject $content -Pattern $oldValue -AllMatches
$replacedTotal = $toBeReplaced.Matches.Count
$content | ForEach-Object {$_ -replace "$oldValue", "$newValue"} | Set-Content $filePath

I need my script to accept input from a file rather than read-host

I have a ps script which will ask for a number, then search for that number in a location with 1000s of files, copy those file names having those number and then output it to a file. That number is also saved in a txt file in a different location, from which I manually copy and insert into this script. Is it possible to make the script read from the 2nd line onwards of the file containing the number, then search for that number within files, like it is doing now?
This is the code I am using:-
$Path = "D:\Projects\MSMQ Journal Messages\PurchaseManagementPO"
$Text = Read-Host -Prompt "PO Number"
$PathArray = #()
$Results = "D:\Chayan\POmiss\miss.txt"
# This code snippet gets all the files in $Path that end in ".xml".
Get-ChildItem $Path -Filter "*.xml" |
Where-Object { $_.Attributes -ne "Directory"} |
ForEach-Object {
If (Get-Content $_.FullName | Select-String -Pattern $Text)
{
$PathArray += $_.FullName
$PathArray += $_.FullName
}
}
Write-Host "Contents of ArrayPath:"
$PathArray | % {$_} | Out-File "D:\Chayan\POmiss\miss.txt" -Append
That PO Number comes from a file, which is generated through a different script, and gets saved like below:-
ponumMaster
908859
280973
I manually put these number in the read-host to do the search and save file name. Is there a way powershell can copy these numbers from this file and do the task?
You should be able to use -skip to move past the first line
The example below would skip the first line and give the results after that
get-content C:\_lab\test.txt | select -skip 1
This example would skip the first line and only give the results from the second line
get-content C:\_lab\test.txt | select -first 1 -skip 1
For your script, you should just need to do the following:
$Text = get-content C:\_lab\test.txt | select -skip 1
#we clear this variable so it can be run multiple times in the same session
clear-variable final -ErrorAction Ignore
#grab txt file content and split into an array
[array]$txt=(get-content "D:\Chayan\POmiss\miss.txt") -split " "
#take out the blanks and assign to new variable called final (we clear this above so it can be run multiple times in the same session)
foreach($line in $txt){
if($line.replace(" ","")){
[array]$final+=$line
}
}
#run script, calling the variable $text in place of the numbers
foreach($text in $final){
(your normal script here)
}

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
}

Search for Multiple strings in a text file in Powershell

I have a text 'File.txt'. There are 100's of lines.
The file contains the string 'XX' (in any line), 'YY' (in any line) and 'ZZ' (in any line).
I want to check if the text file really contains 'XX' or 'YY' or 'ZZ'. If it yes then exit the script.
I'm not sure how to give multiple search patterns in the below line Or any modification to this existing code would help.
$myString = Select-String -Path C:\Temp\File.txt -Pattern "XX"
Edited Code:
$myFile = Get-Content -Path 'C:\file.txt | Out-String
if (Select-String $myFile -Pattern 'XX|YY' -NotMatch)
{
Do something else
}
Select-String accepts a regular expression as a pattern, so you can just use a logical OR to check for all three strings:
if ( Select-String -Path C:\Temp\File.txt -Pattern 'XX|YY|ZZ' ) {
echo "yes"
# Do something else
}

Powershell issue with do while loop

I've got a simple bit of code that looks for a string in a series of log files.
If it finds the string, it should exit the loop (nested inside another loop as part of a function) with $buildlogsuccess = 'True'
If it can't find the string, it should exit and return $buildlogsuccess = 'False'
The select-string statement itself works, however it looks like there's something wrong with the below code:
$logArr = gci C:\build\Logs | where {($_.name -like 'install*.log') -and (! $_.PSIsContainer)} | select -expand FullName
$count = ($logArr).count
Foreach ($log in $logArr) {
Do {
$count -= 1
$buildlogsuccess = [bool](select-string -path $log -simplematch $buildstring)
If (($buildlogsuccess)) {break}
} while ($count -gt '0')
}
When one of the logs has the string, the loop finishes and should return $buildlogsuccess as 'True'.
If I check $log it shows the file that I know has the string (in this instance C:\build\Logs\Installer1.log).
Strangely, at this point $count shows as having a value of -1?
If I take the string out of that file and run again it also exits and returns the correct variable value (and shows the $log variable as the last file in $logArr as expected), but this time $count shows as -24.
My code is also returning $buildlogsuccess as 'False' when the string is present in one of the log files.
Re-tested [bool](select-string -path $log -simplematch $buildstring) by manually populating $log (with a file that has that string) and $buildstring and get 'True' as expected when using
[bool](select-string -path $log -simplematch $buildstring)
Note: Variables it uses:
$buildstring = "Package
'F:\xxx\Bootstrap\apackage\Installsomething.xml' processed
successfully"
Any help identifying where I've gone wrong would be appreciated.
Your code can be greatly simplified:
$buildlogsuccess = Select-String -SimpleMatch -Quiet $buildstring C:\build\Logs\install*.log
The above assumes that there are no directories that match install*.log; if there's a chance of that, pipe the output of Get-ChildItem -File C:\build\Logs -Filter install*.log to Select-String instead.
Do-while will first do the thing, then check the while statement. You're iterating over n files. It doesn't check the value of $count before it executes that portion.
So let's say the first file does not contain the string you're looking for. It will (correctly) decrement the $count variable to zero, and then it moves on to the next $log in $logArr.
Now for each next file in the folder it will decrement $count, and then exit the loop when it sees that $count is not greater than 0.
I don't know why you're using the do-while loop at all here
Thanks Norsk
I over-complicated for myself.
This worked:
$logArr = gci C:\build\Logs | where {($_.name -like 'install*.log') -and (! $_.PSIsContainer)} | select -expand FullName
$count = ($logArr).count
Foreach ($log in $logArr) {
$buildlogsuccess = [bool](select-string -path $log -simplematch $buildstring)
If ($buildlogsuccess) {break}
}