I have a simple script that is suppose to find text and if the right conditions are met, it will replace the text:
foreach ($line in $file){
$line.replace("â‡","+") | Out-File -FilePath $destination -Append
if ($line -eq "â‡RECORD_IDENTIFIER:"){
$line.replace("â‡RECORD_IDENTIFIER:","+RECORD_IDENTIFIER: A") | Out-File -FilePath $destination -Append
}
if ($line -eq "â‡END_TAG"){
$line.replace("â‡END_TAG","+END_TAG;") | Out-File -FilePath $destination -Append
}
but the result is this:
+START_TAG:
+DATA_FILE_(DATE/TIME):2017-02-13T13:44:44.489-08:00
+RECORD_IDENTIFIER:
+RECORD_IDENTIFIER: A
+CLIENT_NUM:8802
+SOLOMON_ID:TRUG01
I only want it to produce one RECORD_IDENTIFIER.
This is where you need to discover the Switch operator. I think it's an operator, maybe a cmdlet? Whatever it is, its what you need! What it does is compare the current iteration of a collection against multiple cases, and applies the cases that match. For example:
Switch ($file) {
"â‡RECORD_IDENTIFIER:" {
$_.Replace("â‡RECORD_IDENTIFIER:", "+RECORD_IDENTIFIER: A") |
Out-File -FilePath $destination -Append
Continue
}
"â‡END_TAG" {
$_.Replace("â‡END_TAG", "+END_TAG;") |
Out-File -FilePath $destination -Append
Continue
}
default {
$_.Replace("â‡", "+") |
Out-File -FilePath $destination -Append
}
}
The Continue commands tell the loop to move to the next item, so if it matches the first case it will do your replace, output it to file, and then move to the next line. If it does not match the first case it tries the second, and if it matches it will do the replace for that, output to file, and move to the next line. If it doesn't match either of the first two cases it reaches the default line, and everything has that scriptblock applied to it, so anything that didn't match the first two cases has the replace("â‡","+") performed, and output to file.
Related
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.
I'm completely new to Powershell and trying to accomplish a small task. I'm trying to find a string in the last two lines of a log file, and if the value doesn't match, take and action. If it matches, end. I think that's the simplest way to explain it. :-) I'm searching for certain words, if not found, open notepad, if found, exit. I've pieced some of it together, but I'm not sure how to handle the If False, run notepad portion. Would love some help. Thanks.
if (Test-Path C:\windows\ccm\logs\CcmEval.log) {
Get-Content 'C:\windows\ccm\logs\CcmEval.log' -Tail 2 | Select-String "Updating MDM_ConfigSetting.ClientHealthStatus with value 7" | % { $_ -notmatch "value 7" }
Start-Process C:\windows\notepad.exe
}
You can do that with that with one more if else condition. If condition not matches it will open notepad otherwise it will execute your command to exit.
if (Test-Path C:\windows\ccm\logs\CcmEval.log) {
$condition = Get-Content 'C:\windows\ccm\logs\CcmEval.log' -Tail 2 | Select-String "Updating MDM_ConfigSetting.ClientHealthStatus with value 7" | % { $_ -notmatch "value 7" }
if (!$condition) {
Start-Process C:\windows\notepad.exe
}
else {
"Do something here"
}
}
You can drop the Test-Path if you add -ErrorAction SilentlyContinue to the Get-Content, so it will quietly return nothing if the file is not found. This means you can use the same code whether or not the file exists.
-match takes a regular expression pattern, which is powerful enough that you can build "not 7" into it in one go using [^7] to mean "any character except 7".
$logFile = 'C:\windows\ccm\logs\CcmEval.log'
$lines = Get-Content -Path $logFile -Tail 2 -ErrorAction SilentlyContinue
if ($lines -match "Updating MDM_ConfigSetting.ClientHealthStatus with value [^7]") {
Start-Process C:\windows\notepad.exe
}
I have a CSV which I process using powershell where occasionally one or more of the rows will be missing one of the comma delimiters. It will always have 3 columns and the 2nd column is optional.
Ex.
Col1,Col2,Col3
SomeCol1Val,,SomeCol3Val
AnotherCol1Val,AnotherCol3Val
In the above example I need to add another comma to Row #2
I've been able to determine which row needs to be updated and how change the value, but I'm not sure how overwrite that specific row in the file.
$csvFile = Get-Content "C:\MyFile.csv"
foreach($row in $csvFile) {
$cnt = ($row.ToCharArray() -eq ',').count
if ($cnt -eq 1) {
$row = $row -replace ",",",,"
}
}
Thanks
As Doug Maurer points out, all that is missing from your code is to write the updated $row values back to your input file, using the Set-Content cmdlet.
However, I suggest a different, faster approach, using a switch statement with the -File option and a single -replace operation based on a regex.
$csvFile = 'C:\MyFile.csv'
$newContent =
switch -File $csvFile {
default { $_ -replace '^([^,]+),([^,]+)$', '$1,,$2' }
}
Set-Content $csvFile -Value $newContent -WhatIf
Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.
Note that you may have to use the -Encoding parameter to specify the desired character encoding, which in Windows PowerShell is the active ANSI code page and in PowerShell [Core] v6+ BOM-less UTF-8.
If you wanted to stick with your original approach:
$csvFile = 'C:\MyFile.csv'
$newContent =
foreach ($row in Get-Content $csvFile) {
if (($row.ToCharArray() -eq ',').Count -eq 1) {
$row -replace ',', ',,'
} else {
$row
}
}
Set-Content $csvFile -Value $newContent -WhatIf
Note that both approaches collect all (modified) lines in memory as a whole, so as to speed up the operation and also to allow writing back to the input file.
However, it is possible to stream the output, to a different file - i.e. to write the output file line by line - by enclosing the switch statement in & { ... } and piping that to Set-Content. With your Get-Content approach you'd have to use
Get-Content ... | ForEach-Object { ... } | Set-Content instead.
I have put together a script inspired from a number of sources. The purpose of the powershell script is to scan a directory for files (.SQL), copy all of it to a new directory (retain the original), and scan each file against a list file (CSV format - containing 2 columns: OldValue,NewValue), and replace any strings that matches. What works: moving, modifying, log creation.
What doesn't work:
Recording in the .log for the changes made by the script.
Sample usage: .\ConvertSQL.ps1 -List .\EVar.csv -Files \SQLFiles\Rel_1
Param (
[String]$List = "*.csv",
[String]$Files = "*.sql"
)
function Get-TimeStamp {
return "[{0:dd/MM/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
}
$CustomFiles = "$Files\CUSTOMISED"
IF (-Not (Test-Path $CustomFiles))
{
MD -Path $CustomFiles
}
Copy-Item "$Files\*.sql" -Recurse -Destination "$CustomFiles"
$ReplacementList = Import-Csv $List;
Get-ChildItem $CustomFiles |
ForEach-Object {
$LogFile = "$CustomFiles\$_.$(Get-Date -Format dd_MM_yyyy).log"
Write-Output "$_ has been modified on $(Get-TimeStamp)." | Out-File "$LogFile"
$Content = Get-Content -Path $_.FullName;
foreach ($ReplacementItem in $ReplacementList)
{
$Content = $Content.Replace($ReplacementItem.OldValue, $ReplacementItem.NewValue)
}
Set-Content -Path $_.FullName -Value $Content
}
Thank you very much.
Edit: I've cleaned up a bit and removed my test logging files.
Here's the snippet of code that I've been testing with little success. I put the following right under $Content= Content.Replace($ReplacementItem.OldValue, $ReplacementItem.NewValue)
if ( $_.FullName -like '*TEST*' ) {
"This is a test." | Add-Content $LogFile
}
I've also tried to pipe out the Set-Content using Out-File. The outputs I end up with are either a full copy of the contents of my CSV file or the SQL file itself. I'll continue reading up on different methods. I simply want to, out of hundreds to a thousand or so lines, to be able to identify what variables in the SQL has been changed.
Instead of piping output to Add-Content, pipe the log output to: Out-File -Append
Edit: compare the content using the Compare-Object cmdlet and evaluate it's ouput to identify where the content in each string object differs.
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
}