Powershell issue with do while loop - powershell

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

Related

Script lists all files that don't contain needed content

I'm trying to find all files in a dir, modified within the last 4 hours, that contain a string. I can't have the output show files that don't contain needed content. How do I change this so it only lists the filename and content found that matches the string, but not files that don't have that string? This is run as a windows shell command. The dir has a growing list of hundreds of files, and currently output looks like this:
File1.txt
File2.txt
File3.txt
... long long list, with none containing the needed string
(powershell "Set-Location -Path "E:\SDKLogs\Logs"; Get-Item *.* | Foreach { $lastupdatetime=$_.LastWriteTime; $nowtime = get-date; if (($nowtime - $lastupdatetime).totalhours -le 4) {Select-String -Path $_.Name -Pattern "'Found = 60.'"| Write-Host "$_.Name Found = 60"; }}")
I tried changing the location of the Write-Host but it's still printing all files.
Update:
I'm currently working on this fix. Hopefully it's what people were alluding to in comments.
$updateTimeRange=(get-date).addhours(-4)
$fileNames = Get-ChildItem -Path "K:\NotFound" -Recurse -Include *.*
foreach ($file in $filenames)
{
#$content = Get-Content $_.FullName
Write-host "$($file.LastWriteTime)"
if($file.LastWriteTime -ge $($updateTimeRange))
{
#Write-Host $file.FullName
if(Select-String -Path $file.FullName -Pattern 'Thread = 60')
{
Write-Host $file.FullName
}
}
}
If I understood you correctly, you just want to display the file name and the matched content? If so, the following will work for you:
$date = (Get-Date).AddHours(-4)
Get-ChildItem -Path 'E:\SDKLogs\Logs' | Where-Object -FilterScript { $date -lt $_.LastWriteTime } |
Select-String -Pattern 'Found = 60.' |
ForEach-Object -Process {
'{0} {1}' -f $_.FileName, $_.Matches.Value
}
Get-Date doesn't need to be in a variable before your call but, it can become computationally expensive running a call to it again and again. Rather, just place it in a variable before your expression and call on the already created value of $date.
Typically, and for best practice, you always want to filter as far left as possible in your command. In this case we swap your if statement for a Where-Object to filter as the objects are passed down the pipeline. Luckily for us, Select-String returns the file name of a match found, and the matched content so we just reference it in our Foreach-Object loop; could also use a calculated property instead.
As for your quoting issues, you may have to double quote or escape the quotes within the PowerShell.exe call for it to run properly.
Edit: swapped the double quotes for single quotes so you can wrap the entire expression in just PowerShell.exe -Command "expression here" without the need of escaping; this works if you're pattern to find doesn't contain single quotes.

Unable to compare the integer with the operator in powershell?

Unbale to compare the interger value in powershell using the -gt or -ge operator
Iam trying to get the latest number of files as a count in to a int variable. After i got the count, i need to check if it is greater than n number. But it doesn't look the value is comparing.
[int]$count_of_Files= Write-Host (Get-ChildItem $source_dir | Measure-Object).Count
if($count_of_Files -gt 3){Write-Host "True
"}
The count_of_files value is 6. But when i ran the if condition to compare the value > 3 , the output "True" not printing
Appreciate if anyone can help on this?
When creating a variable, do not use the Write-Host command ever, it will not assign it to the variable thus making it a NULL variable. Also try not to use strings for a Boolean when you can use $True or $False
[int]$count_of_Files = (Get-ChildItem $source_dir | Measure-Object).Count
IF($count_of_Files -gt 3){
$True
} Else {
$False
}
Comment Answer: Normally you should ask a new question if it is not related to your initial question, but since it is a simple adjustment of the original code, please see below.
# Get files only from $source_dir
$Files = Get-ChildItem $source_dir -File
# Created if statement to check if there are more than 3 files in the $source_dir
IF($Files.Count -gt 3){
# Assigns the top 3 files to $files_to_keep with most recent at the top
$files_to_keep = $Files | Sort-Object LastWriteTime -Descending | Select-Object -first 3
}

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

Check text file content in PowerShell

The PowerShell command
Get-ADFSRelyingPartyTrust | select Name | out-file C:\listOfNames.txt
generates a file as follows:
Name
----
AustriaRP
BahamasRP
BrazilRP
CanadaRP
[...]
Now, how can I check if BrazilRP has been extracted and C:\listOfNames.txt contains it?
The Get-Content and then Select-String should help. If the string is in the file it will get returned. If not then the command will returned empty value.
Get-Content C:\listOfNames.txt | Select-String "BrazilRP"
If the "BrazilRP" occurs more than once all the occurrences will returned so you know if you got any duplicates. Same holds if the string is a part of a longer expression. For example if you search for "zil" then "BrazilRP" will be returned as well.
Also you can pipe the results out to another file:
Get-Content C:\listOfNames.txt | Select-String "BrazilRP" | Out-File C:\myResults.txt
I found a solution (but thanks to PiotrWolkowski to suggest me the Get-Content function):
$file = Get-Content "C:\listOfNames.txt"
$containsWord = $file | %{$_ -match "BrazilRP"}
if ($containsWord -contains $true) {
Write-Host "There is!"
} else {
Write-Host "There ins't!"
}
If you want to easily see if a file contains your text try this
The [bool] type returns the data as either true or false instead of returning the actual data your searching for
if ([bool]((Get-Content -Path "C:\listOfNames.txt") -like '*BrazilRP*')) {
write-host "found it"
}
else {
write-host "didnt find it"
}

How can I search the first line and the last line in a text file?

I need to only search the 1st line and last line in a text file to find a "-" and remove it.
How can I do it?
I tried select-string, but I don't know to find the 1st and last line and only remove "-" from there.
Here is what the text file looks like:
% 01-A247M15 G70
N0001 G30 G17 X-100 Y-100 Z0
N0002 G31 G90 X100 Y100 Z45
N0003 ; --PART NO.: NC-HON.PHX01.COVER-SHOE.DET-1000.050
N0004 ; --TOOL: 8.55 X .3937
N0005 ;
N0006 % 01-A247M15 G70
Something like this?
$1 = Get-Content C:\work\test\01.I
$1 | select-object -index 0, ($1.count-1)
Ok, so after looking at this for a while, I decided there had to be a way to do this with a one liner. Here it is:
(gc "c:\myfile.txt") | % -Begin {$test = (gc "c:\myfile.txt" | select -first 1 -last 1)} -Process {if ( $_ -eq $test[0] -or $_ -eq $test[-1] ) { $_ -replace "-" } else { $_ }} | Set-Content "c:\myfile.txt"
Here is a breakdown of what this is doing:
First, the aliases for those now familiar. I only put them in because the command is long enough as it is, so this helps keep things manageable:
gc means Get-Content
% means Foreach
$_ is for the current pipeline value (this isn't an alias, but I thought I would define it since you said you were new)
Ok, now here is what is happening in this:
(gc "c:\myfile.txt") | --> Gets the content of c:\myfile.txt and sends it down the line
% --> Does a foreach loop (goes through each item in the pipeline individually)
-Begin {$test = (gc "c:\myfile.txt" | select -first 1 -last 1)} --> This is a begin block, it runs everything here before it goes onto the pipeline stuff. It is loading the first and last line of c:\myfile.txt into an array so we can check for first and last items
-Process {if ( $_ -eq $test[0] -or $_ -eq $test[-1] ) --> This runs a check on each item in the pipeline, checking if it's the first or the last item in the file
{ $_ -replace "-" } else { $_ } --> if it's the first or last, it does the replacement, if it's not, it just leaves it alone
| Set-Content "c:\myfile.txt" --> This puts the new values back into the file.
Please see the following sites for more information on each of these items:
Get-Content uses
Get-Content definition
Foreach
The Pipeline
Begin and Process part of the Foreach (this are usually for custom function, but they work in the foreach loop as well)
If ... else statements
Set-Content
So I was thinking about what if you wanted to do this to many files, or wanted to do this often. I decided to make a function that does what you are asking. Here is the function:
function Replace-FirstLast {
[CmdletBinding()]
param(
[Parameter( `
Position=0, `
Mandatory=$true)]
[String]$File,
[Parameter( `
Position=1, `
Mandatory=$true)]
[ValidateNotNull()]
[regex]$Regex,
[Parameter( `
position=2, `
Mandatory=$false)]
[string]$ReplaceWith=""
)
Begin {
$lines = Get-Content $File
} #end begin
Process {
foreach ($line in $lines) {
if ( $line -eq $lines[0] ) {
$lines[0] = $line -replace $Regex,$ReplaceWith
} #end if
if ( $line -eq $lines[-1] ) {
$lines[-1] = $line -replace $Regex,$ReplaceWith
}
} #end foreach
}#End process
end {
$lines | Set-Content $File
}#end end
} #end function
This will create a command called Replace-FirstLast. It would be called like this:
Replace-FirstLast -File "C:\myfiles.txt" -Regex "-" -ReplaceWith "NewText"
The -Replacewith is optional, if it is blank it will just remove (default value of ""). The -Regex is looking for a regular expression to match your command. For information on placing this into your profile check this article
Please note: If you file is very large (several GBs), this isn't the best solution. This would cause the whole file to live in memory, which could potentially cause other issues.
try:
$txt = get-content c:\myfile.txt
$txt[0] = $txt[0] -replace '-'
$txt[$txt.length - 1 ] = $txt[$txt.length - 1 ] -replace '-'
$txt | set-content c:\myfile.txt
You can use the select-object cmdlet to help you with this, since get-content basically spits out a text file as one huge array.
Thus, you can do something like this
get-content "path_to_my_awesome_file" | select -first 1 -last 1
To remove the dash after that, you can use the -Replace switch to find the dash and remove it. This is better than using System.String.Replace(...) method because it can match regex statements and replace whole arrays of strings too!
That would look like:
# gc = Get-Content. The parens tell Powershell to do whatever's inside of it
# then treat it like a variable.
(gc "path_to_my_awesome_file" | select -first 1 -last 1) -Replace '-',''
If your file is very large you might not want to read the whole file to get the last line. gc -Tail will get the last line very quickly for you.
function GetFirstAndLastLine($path){
return New-Object PSObject -Property #{
First = Get-Content $path -TotalCount 1
Last = Get-Content $path -Tail 1
}
}
GetFirstAndLastLine "u_ex150417.log"
I tried this on a 20 gb log file and it returned immediately. Reading the file takes hours.
You will still need to read the file if you want to keep all excising content and you want only to remove from the end. Using the -Tail is a quick way to check if it is there.
I hope it helps.
A cleaner answer to the above:
$Line_number_were_on = 0
$Awesome_file = Get-Content "path_to_ridiculously_excellent_file" | %{
$Line = $_
if ($Line_number_were_on -eq $Awesome_file.Length)
{ $Line -Replace '-','' }
else
{ $Line } ;
$Line_number_were_on++
}
I like one-liners, but I find that readability tends to suffer sometimes when I put terseness over function. If what you're doing is going to be part of a script that other people will be reading/maintaining, readability might be something to consider.
Following Nick's answer: I do need to do this on all text files in the directory tree and this is what I'm using now:
Get-ChildItem -Path "c:\work\test" -Filter *.i | where { !$_.PSIsContainer } | % {
$txt = Get-Content $_.FullName;
$txt[0] = $txt[0] -replace '-';
$txt[$txt.length - 1 ] = $txt[$txt.length - 1 ] -replace '-';
$txt | Set-Content $_.FullName
}
and it looks like it's working well now.
Simple process:
Replace $file.txt with your filename
Get-Content $file_txt | Select-Object -last 1
I was recently searching for comments in the last line of .bat files. It seems to mess up the error code of previous commands. I found this useful for searching for a pattern in the last line of files. Pspath is a hidden property that get-content outputs. If I used select-string, I would lose the filename. *.bat gets passed as -filter for speed.
get-childitem -recurse . *.bat | get-content -tail 1 | where { $_ -match 'rem' } |
select pspath
PSPath
------
Microsoft.PowerShell.Core\FileSystem::C:\users\js\foo\file.bat