PowerShell query: how to process a file - powershell

I often want to process a file resulting in a modified output file. I can't seem to find the PowerShell way to do this - it always ends up looking like code in a one-liner, IYSWIM. This is an example.
I have an LMHOSTS file. Don't ask why! I want to get a new LMHOSTS file that only contains servers that respond to pings. I also want to pass through any comment lines.
Comment lines begin with a #.
Data lines are something like this (space-separated):
10.123.177.1 SVRDS1 #PRE #DOM:DOM1
or this:
10.241.177.30 SVRDS30 #PRE
This is what I've got (with thanks to Luke for help with Ping-Host):
gc 'C:\work\lmhosts.txt' | % { if ($_ -like '#*') { $_ | out-file 'C:\work\lmhosts2.txt' -append } else { if ($(Ping-Host $_.Substring(0, $_.indexof(' ')) -count 1 -timeout 10).received -eq 1) { $_ | out-file 'C:\work\lmhosts2.txt' -append } } }
It works but it's not nice. I've taken a programmer's approach but done it as a one-liner. Is there a better way of doing it, that's more 'shell' and less 'code'?

In this particular example, you are filtering the contents of 1 file and outputting it to another. Where-Object is usually a good choice for this. You can then simplify your one-liner a bit:
gc 'C:\work\lmhosts.txt' |
?{ ($_ -like '#*') -or
($(Ping-Host $_.Split(' ')[0]) -count 1 -timeout 10).received -eq 1) } >
'C:\work\lmhosts2.txt'
Some notes:
"?" is an alias for Where-Object
The -or operator will short circuit, that is, if the first operand results in True, the second will not bother executing.
You can use the redirection operator ">" instead of "| Out-File".
I replaced Substring with Split, it seemed slightly simpler, not sure if works in general with your input.

Whitespace is your friend! In Powershell scripts I write that aren't immediately thrown away, almost every '|' is followed by a newline + indent.

Related

Read text file and check for value in a specific position and change when true

I need to loop through multiple text files and check for a $ value in position 7 on each line of text and replace it with an * when found. But ONLY when it is in position 7. I do not want to change it if it is found in other positions. This is as far as I have gotten. Any help would be greatly appreciated.
Get-ChildItem 'C:\*.txt' -Recurse |
foreach $line in Get-Content $_ {
$linePosition1to5 = $line.Substring(0,6)
$linePosition7 = $line.Substring(6,1)
$linePositionRest = $line.Substring(8)
if($linePosition7 = "$"){
$linePosition7 = "*"
}
$linePosition1to5 + $linePosition7 + $linePositionRest |
Set-Content $_
}
Is there something that doesn't work in your example, or just that all the nested substrings are annoying to work with?
I'd use regex for this one. e.g.
$Lines = Get-Content -Path "C:\examplefile.txt" -raw
$Lines -replace '(?m)(^.{6})\$', '$1*'
To explain the regex:
?m indicates that it's multiline, required because I used raw get-content rather than pulling an array. Array would work too, just needs a loop like you did.
^.{6} line start plus any 6 characters (capture group 1)
$ escaped dollar character
$1* Capture group 1 left as is, dollar replaced with *, anything else not captured and therefore left untouched.
Thanks for code and the explanation. I realized that I left out the -raw option and it did work. Putting it back in it seems to add a line to the end of each file. Unless you can think of reason why I shouldn't I was going to leave it out.
Get-ChildItem 'C:\TEST\*.txt' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '(?m)(^.{6})\$', '$1*'}) |
Set-Content $_
}

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
}

Skipping every 1 line using powershell

I want to write a script that would skip 1 line every time.
My text file looks like below :
Java 8 update
{243453-4544-34534-6565-7676772345}
Java 7 update
{23444-554-565767-435234-5426564647}
I want to write PowerShell script that should skip string.
Expected output:
{243453-4544-34534-6565-7676772345}
{23444-554-565767-435234-5426564647}
This is example text file but I have 200 lines of text file which is same format(1 line string and next line is product code).
Kindly help on this.
I got the answer using following script.
$codes= Get-Content %path to text file" | where {$_ -notmatch 'Java'}
Foreach($code in $codes)
{
write-host $code
}
You can try getting all the lines that don't contain 'java':
Get-Content .\data.txt |
Where-Object {$_ -notlike "*java*"}
This assumes the non-valid lines contain that word, but doesn't guarantee that the returned ones contain a correct value. It is probably better to do a positive match on the string you want, like this:
Get-Content .\data.txt |
Select-String -Pattern "{(\d+-){4}\d+}"
This will get the lines containing the number pattern, no matter how they are spaced (so, even if they are are the 1st, 9th, 12th and 28th lines).
Finally, if you really want every second line, regardless of content, try the modulus operator (%):
$i=1
Get-Content .\data.txt |
Where-Object {-not ($i++ % 2)}
the nice thing about this technique is you can get every 3rd line by replacing the '2' with '3', or every 4th line by replacing with a '4', etc.
I got the required output by using below code
$code= Get-Content %path of file% | Select-string '^{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}$'
There are tons of ways. The not Java works if always Java.
You could introduce an if statement in your loop checking remainder of a variable.
$check=1
Foreach($line in (gc file.tx)){
If (($check % 2) -eq 0){
Do commands
}
$check = $check + 1
}
Also changing -eq 0 to 1 will return the opposite lines

Powershell/Batch Files: Verify that a file contains at least one entry from a list of strings

Here is my current issue: I have a list of 1800 customer numbers (ie 123456789). I need to determine which of these numbers show up in another, much larger (4 gb) file. The larger file is a fixed-width file of all customer information. I know how I would do this in SQL, but like I said it's a flat file.
When searching for individual numbers, I was using a command I found elsewhere on this site which worked very well:
get-content CUSTOMERINFO.txt -ReadCount 1000 | foreach { $_ -match "123456789" }
However, I do not have the expertise to translate this into another command, or a batch file, which would load list.txt and search all lines in customerinfo.txt for the requisite strings.
Time is not a major constraint, as this is running on a test server and will be a once-off project.
Thank you very much for any help you can provide.
So I appreciate everyone's help. Everybody gave me helpful info that let me get to my final solution, so I appreciate it. Especially to the guy who asked if this was a codewriting request, because it made me realize I needed to just write some code.
For anyone else who runs into the same problem, here is the code I ended up using:
$matches = Get-Content .\list.txt
foreach ($entry in $matches)
{ $results = get-content FiletoSearch -ReadCount 1000 | foreach { $_ -match $entry }
if ($results -eq $null) {
$entry }
else {
"found"}
}
This gives a 'found' entry for everything that was found (which is information I don't need), and gives back the value searched for when it's not found (which is information I do need).
The match comparator can work over multiple values, you can separate them with a bar | character.
e.g.
get-content CUSTOMERINFO.txt -ReadCount 1000 | foreach { $_ -match "DEF|YZ" }
You can also read the contents of a file and replace newlines with a character of your choice. So if list.txt is a list of values to search, such as
DEF
XY
Then you can read it and convert it to a bar-separated list using the join operator:
(Get-Content list.txt) -join "|"
Put them together and you should have your solution:
$listSearch = (Get-Content list.txt) -join "|";
get-content CUSTOMERINFO.txt -ReadCount 1000 | foreach { $_ -match $listSearch}

Pulling a substring for each line in file

Using Powershell, I am simply trying to pull 15 characters starting from the 37th position of any record that begins with a 6. I'd like to loop through and generate a record for each instance so it can later be put into an output file. But I seem to not be hitting the correct syntax just to return the 15 characters I know I am missing something obvious. Been at this for a while. Here is my script:
$content = Get-Content -Path .\tmfhsyst*.txt | Where-Object { $_.StartsWith("6") }
foreach ($line in $contents)
{
$val102 = $line.substring(36,15)
}
write-output $val102
Just as Bill_Stewart pointed out, you need to move your Write-Output line inside the ForEach loop. A possibly better way to do it would just be to pipe it:
Get-Content -Path .\tmfhsyst*.txt | Where-Object { $_.StartsWith("6") } | foreach{$_.substring(36,15)}
That should give you the output you desired.
Using Substring() has the disadvantage that it will raise an error if the string is shorter than start index + substring length. You can avoid this with a regular expression match:
(Get-Content -Path .\tmfhsyst*.txt) -match '^6.{35}(.{15})' | % { $matches[1] }