How do I add a newline to command output in PowerShell? - powershell

I run the following code using PowerShell to get a list of add/remove programs from the registry:
Get-ChildItem -path hklm:\software\microsoft\windows\currentversion\uninstall `
| ForEach-Object -Process { Write-Output $_.GetValue("DisplayName") } `
| Out-File addrem.txt
I want the list to be separated by newlines per each program. I've tried:
Get-ChildItem -path hklm:\software\microsoft\windows\currentversion\uninstall `
| ForEach-Object -Process { Write-Output $_.GetValue("DisplayName") `n } `
| out-file test.txt
Get-ChildItem -path hklm:\software\microsoft\windows\currentversion\uninstall `
| ForEach-Object {$_.GetValue("DisplayName") } `
| Write-Host -Separator `n
Get-ChildItem -path hklm:\software\microsoft\windows\currentversion\uninstall `
| ForEach-Object -Process { $_.GetValue("DisplayName") } `
| foreach($_) { echo $_ `n }
But all result in weird formatting when output to the console, and with three square characters after each line when output to a file. I tried Format-List, Format-Table, and Format-Wide with no luck. Originally, I thought something like this would work:
Get-ChildItem -path hklm:\software\microsoft\windows\currentversion\uninstall `
| ForEach-Object -Process { "$_.GetValue("DisplayName") `n" }
But that just gave me an error.

Or, just set the output field separator (OFS) to double newlines, and then make sure you get a string when you send it to file:
$OFS = "`r`n`r`n"
"$( gci -path hklm:\software\microsoft\windows\currentversion\uninstall |
ForEach-Object -Process { write-output $_.GetValue('DisplayName') } )" |
out-file addrem.txt
Beware to use the ` and not the '. On my keyboard (US-English Qwerty layout) it's located left of the 1.
(Moved here from the comments - Thanks Koen Zomers)

Give this a try:
PS> $nl = [Environment]::NewLine
PS> gci hklm:\software\microsoft\windows\currentversion\uninstall |
ForEach { $_.GetValue("DisplayName") } | Where {$_} | Sort |
Foreach {"$_$nl"} | Out-File addrem.txt -Enc ascii
It yields the following text in my addrem.txt file:
Adobe AIR
Adobe Flash Player 10 ActiveX
...
Note: on my system, GetValue("DisplayName") returns null for some entries, so I filter those out. BTW, you were close with this:
ForEach-Object -Process { "$_.GetValue("DisplayName") `n" }
Except that within a string, if you need to access a property of a variable, that is, "evaluate an expression", then you need to use subexpression syntax like so:
Foreach-Object -Process { "$($_.GetValue('DisplayName'))`r`n" }
Essentially within a double quoted string PowerShell will expand variables like $_, but it won't evaluate expressions unless you put the expression within a subexpression using this syntax:
$(`<Multiple statements can go in here`>).

I think you had the correct idea with your last example. You only got an error because you were trying to put quotes inside an already quoted string. This will fix it:
gci -path hklm:\software\microsoft\windows\currentversion\uninstall | ForEach-Object -Process { write-output ($_.GetValue("DisplayName") + "`n") }
Edit: Keith's $() operator actually creates a better syntax (I always forget about this one). You can also escape quotes inside quotes as so:
gci -path hklm:\software\microsoft\windows\currentversion\uninstall | ForEach-Object -Process { write-output "$($_.GetValue(`"DisplayName`"))`n" }

Ultimately, what you're trying to do with the EXTRA blank lines between each one is a little confusing :)
I think what you really want to do is use Get-ItemProperty. You'll get errors when values are missing, but you can suppress them with -ErrorAction 0 or just leave them as reminders. Because the Registry provider returns extra properties, you'll want to stick in a Select-Object that uses the same properties as the Get-Properties.
Then if you want each property on a line with a blank line between, use Format-List (otherwise, use Format-Table to get one per line).
gci -path hklm:\software\microsoft\windows\currentversion\uninstall |
gp -Name DisplayName, InstallDate |
select DisplayName, InstallDate |
fl | out-file addrem.txt

The option that I tend to use, mostly because it's simple and I don't have to think, is using Write-Output as below. Write-Output will put an EOL marker in the string for you and you can simply output the finished string.
Write-Output $stringThatNeedsEOLMarker | Out-File -FilePath PathToFile -Append
Alternatively, you could also just build the entire string using Write-Output and then push the finished string into Out-File.

Related

Powershell search directory for code files with text matching input a txt file

Data mapping project, in house system to new vendor system. First step is find all the occurrences of current database field names (or column names to be precise) in the C# .cs source files. Trying to use Powershell. Have recently created PS searches with Get-ChildItem and Select-String that work well but the search string array was small and easily hard coded inline. But the application being ported has a couple hundred column names and significant amounts of code. So armed with a text file of all the column names Pipleline would seem like a god tool to create a the basic cross ref for further analysis. However, I was not able to get the Pipeline to work with an external variable anyplace other than first step. Trying using -PipelineVariable, $_. and global variable. Did not find anything specific after lots of searching. P.S. This is my first question to StackoOverflow, be kind please.
Here is what I hoped would work but do dice so far.
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr = [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force |
Select-String $s | Select-Object Path, LineNumber, line | Export-csv $outputfile
}
Did find that this will print the list one time but not twice. In fact it seems using the variable in this way results in processing simply skipping any further pipeline steps.
foreach ($s in $arr) {Write-Host $s | Write $s}
If it isn't possible to do this in Powershell easily my fallback is to do with C# although would much rather get the level up with PowerShell if anyone can point me to the correct understanding of how to do things in the Pipepline, or alternatively construct an equivalent function. Seems like such a natural fit for Powershell.
Thanks.
You're calling Export-csv $outputfile in a loop, which rewrites the whole file in every iteration, so that only the last iteration's output will end up in the file.
While you could use -Append to iteratively append to the output file, it is worth aking a step back: Select-String can accept an array of patterns, causing a line that matches any of them to be considered a match.
Therefore, your code can be simplified as follows:
$inputFile = 'C:\DataColumnsNames.txt'
$outputFile = 'C:\DataColumnsUsages.txt'
Get-ChildItem C:\ProjectFolder -Filter *.cs -Recurse -Force -ea SilentlyContinue |
Select-String -Pattern (Get-Content $inputFile) |
Select-Object Path, LineNumber, line |
Export-csv $outputfile
-Pattern (Get-Content $inputFile) passes the lines of input file $inputFile as an array of patterns to match.
By default, these lines are interpreted as regexes (regular expressions); to ensure that they're treated as literals, add -SimpleMatch to the Select-String call.
This answer to a follow-up question shows how to include the specific pattern among the multiple ones passed to -Pattern that matched on each line in the output.
I think you want to append each occurrence to the csv file. And you need to get the content of the file. Try this:
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force | Foreach {
Get-Content "$_.Fullname" | Select-String $s | Select-Object Path, LineNumber, line | Export-csv -Append -Path "$outputfile"
}
}
-Append was not introduced before powershell v3.0 (Windows 8) then try this:
$inputFile = "C:\DataColumnsNames.txt"
$outputFile = "C:\DataColumnsUsages.txt"
$arr [string[]](Get-Content $inputfile)
foreach ($s in $arr) {
Get-ChildItem -Path "C:ProjectFolder\*" -Filter *.cs -Recurse -ErrorAction SilentlyContinue -Force | Foreach {
Get-Content "$_.Fullname" | Select-String $s | Select-Object Path, LineNumber, line | ConvertTo-CSV -NoTypeInformation | Select-Object -Skip 1 | Out-File -Append -Path "$outputfile"
}
}

How to get regex exact coincidence with a var on Powershell

Another one that doesn't seem to have any solution already online.
I have a file with all my volumes and I have a variable where the user writes a volume such as "C" or "D". The problem here is that if I write "D" it will take me every single line that has a D.
I have this code:
$unit=Read-Host -Prompt 'Introduce a volume name'
echo "Searching for the volume..."
Get-Volume > volumes.txt
get-content volumes.txt | select-string -pattern "$unit" > exist2.txt
gc exist2.txt | where{$_ -ne ""} > exist.txt
$disk=(Get-Content exist.txt)
echo $disk
So the regex should be on the "select-string -pattern" and this is what I've tried so far:
get-content volumes.txt | select-string -pattern "/(^|\W)$unit($|\W)/i"
get-content volumes.txt | select-string -pattern "^[$unit]$"
get-content volumes.txt | select-string -pattern '^$unit,'
get-content volumes.txt | select-string -pattern "\$unit\b"
All of them returns nothing and what I want to return is the line of the D unit.
For example if I write "C" this it what will be returned
C NTFS Fixed Healthy OK
Thank you very much!
This is why PowerShell is an object oriented shell, so you don't have to do this string scraping. A PowerShell way to do this is:
Get-Volume | where-object { $_.DriveLetter -eq $unit -or $_.FriendlyName -eq $unit }
The output on screen does contain a header line, but that's not in the content, the command returns objects, and if you do nothing with them, PowerShell formats them into a table for showing on screen.
If you want to see it without headers
Get-Volume |
where-object { $_.DriveLetter -eq $unit -or $_.FriendlyName -eq $unit } |
format-Table -HideTableHeaders
but if you're going to work with it more in the script, don't convert it to text, it will only make it harder to work with later.

find string from a group of files

I have a group of txt files contain similar strings like this:
Windows 7 Professional Service Pack 1
Product Part No.: *****
Installed from 'Compliance Checked Product' media.
Product ID: 0000-0000-0000 match to CD Key data
CD Key: xxxxx-xxxxx-xxxxx-xxxxx-xxxxx
Computer Name: COMP001
Registered Owner: ABC
Registered Organization:
Microsoft Office Professional Edition 2003
Product ID: 00000-00000-00000-00000
CD Key: xxxxx-xxxxx-xxxxx-xxxxx-xxxxx
How may I pick all office keys one time and save into another file?
My code:
$content = Get-ChildItem -Path 'S:\New folder' -Recurse |
where Name -like "b*" |
select name
Get-Content $content
I get a list of files name but it wouldn't run for Get-Content.
The code you posted doesn't work, because $content contains a list of custom objects with one property (name) containing just the file name without path. Since you're apparently not listing the files in the current working directory, but some other folder (S:\New Folder), you need the full path to those files (property FullName) if you want to be able to read them. Also, the property isn't expanded automatically. You must either expand it when enumerating the files:
$content = Get-ChildItem -Path 'S:\New folder' -Recurse |
Where-Object { $_.Name -like "b*" } |
Select-Object -Expand FullName
Get-Content $content
or when passing the value to Get-Content:
$content = Get-ChildItem -Path 'S:\New folder' -Recurse |
Where-Object { $_.Name -like "b*" } |
Select-Object FullName
Get-Content $content.FullName
With that out of the way, none of the code you have does even attempt to extract the data you're looking for. Assuming that the license information blocks in your files is always separated by 2 or more consecutive line breaks you could split the content of the files at consecutive line breaks and extract the information with a regular expression like this:
Get-ChildItem -Path 'S:\New folder' -Recurse | Where-Object {
-not $_.PSIsContainer -and
$_.Name -like "b*"
} | ForEach-Object {
# split content of each file into individual license information fragments
(Get-Content $_.FullName | Out-String) -split '(\r?\n){2,}' | Where-Object {
# filter for fragments that contain the string "Microsoft Office" and
# match the line beginning with "CD Key: " in those fragments
$_ -like '*Microsoft Office*' -and
$_ -match '(?m)(?<=^CD Key: ).*'
} | ForEach-Object {
# remove leading/trailing whitespace from the extracted key
$matches[0].Trim()
}
} | Set-Content 'C:\output.txt'
(\r?\n){2,} is a regular expression that matches 2 or more consecutive line breaks (both Windows and Unix style).
(?m)(?<=^CD Key: ).* is a regular expression that matches a line beginning with the string CD Key: and returns the rest of the line after that string. (?<=...) is a so-called positive lookbehind assertion that is used for matching a pattern without including it in the returned value. (?m) is a regular expression option that allows ^ to match the beginning of a line inside a multiline string instead of just the beginning of the string.
try Something like this:
Get-ChildItem "c:\temp" -file -filter "*.txt" |
%{select-string -Path $_.FullName -Pattern "CD Key:" } | select line | export-csv "c:\temp\found.csv" -notype
If you want computer information you can do it (-context take N rows before and M rows after example -context 3, 2 take 3 before and 2 after) :
Get-ChildItem "c:\temp" -file -filter "*.txt" |
%{select-string -Path $_.FullName -Pattern "CD Key:" -context 6,0 } | where {$_.Context.PreContext[0] -like 'Computer Name:*'} |
select Line, #{Name="Computer";E={($_.Context.PreContext[0] -split ':')[1] }} | export-csv "c:\temp\found.csv" -notype
Or classically:
Get-ChildItem "c:\temp" -file -filter "*.txt" | foreach{
$CurrenFile=$_.FullName
#split current file rows to 2 column with ':' like delimiter
$KeysValues=get-content $CurrenFile | ConvertFrom-String -Delimiter ":" -PropertyNames Key, Value
#if file contains CD Key, its good file
if ($KeysValues -ne $null -and $KeysValues[2].Key -eq 'CD Key')
{
#build object with asked values
$Object=[pscustomobject]#{
File=$CurrenFile
ComputerName=$KeysValues[3].Value
OfficeKey=$KeysValues[7].Value
}
#send objet to standard output
$Object
}
} | export-csv "c:\temp\found.csv" -notype

Include filename in output

I would like to get content from files in a folder (ignoring the header lines, since some file may ONLY contain the header). But in the output, I would like to include the filename from which the line is read. So far, I have the following:
Get-ChildItem | Get-Content | Where { $_ -notlike "HEADER_LINE_TEXT" } | Out-File -FilePath output_text.txt
I've tried to work with creating a variable in the Where block, $filename=$_.BaseName, and using it in the output, but this didn't work.
EDIT:
I ended up with the following:
Get-ChildItem -Path . |
Where-Object { $_.FullName -like "*records.txt"; $fname=$_FullName; } |
Get-Content |
Select-Object { ($fname + "|" + $_.Trim()) } |
Where { $_ -notlike "*HEADER_LINE_TEXT*" } |
Format-Table -HideTableHeaders |
Out-File -FilePath output_text.txt
This looks lengthy, and can probably be made shorter and clearer. Can someone help with cleaning this up a bit? I'll either post the solution, or vote for a cleaner solution, if one is posted. Thanks.
This looks like a case where it would make it more readable to not make it a one liner at cost of a little additional memory usage.
$InputFolder = "C:\example"
$OutputFile = "C:\example\output_text.txt"
$Files = Get-ChildItem $InputFolder | Where-Object { $_.FullName -like "*records.txt"}
Foreach ($File in $Files) {
$FilteredContent = Get-Content $File.FullName | Where-Object {$_ -notlike "*HEADER_LINE_TEXT*"}
$Output = $FilteredContent | Foreach-Object { "$($File.FullName)|$($_.Trim())" }
$Output | Out-File $OutputFile -Append
}
If you are going to go oneliner style for brevity, you could cut down on length by using position for parameters and using aliases.
Here are a couple other changes:
No need for the second semicolon in your first where block.
I think your variable wasn't working because you were missing the period between $_ and fullname.
Format-Table isn't needed because you already have the string you want to output
You can optimize a little by moving the second where earlier so that you don't trim() on lines you are just going to filter
Looks like you want to use foreach instead of select
Removed the + operator for string concatenation, instead using $() to evaluate inside parenthesis
gci . |
? { $_.FullName -like "*records.txt"; $fname=$_.FullName } |
% { gc $_.FullName } |
? { $_ -notlike "*HEADER_LINE_TEXT*" } |
% { "$fname|$($_.Trim())" } |
Out-File output_text.txt

The following is not increment the file count correctly Any Clue as to why?

This powershell code searches the directory and outputs a list of all the files and how old they are to a log file that is parsed buy a different script. all that is working correctly but i also need to keep track of the number of files it found for that dir and the number of files found globally. Thats what the two foreach-Object statements do. but they are staying at 0.
gci -filter *.avi | Select-Object Name, #{Name="Age"; Expression= { (((Get-Date) - $_.CreationTime).Days) }} | Where {$_.Age -ge $daysToKeep} | Out-File -filepath $logFile -append | Foreach-Object {$fileCountCam1++} | Foreach-Object {$fileCount++}
mjolinor's solution is valid, but there's another way (if you can use v3). You can use Tee-Object to write to the file without a loop.
You can also combine your two variable increments into the same script block in the final foreach-object which will speed things up significantly.
gci -filter *.avi |
Select-Object Name, #{Name="Age"; Expression= { (((Get-Date) - $_.CreationTime).Days) }} |
Where {$_.Age -ge $daysToKeep} | Tee-Object -filepath $logFile -append |
Foreach-Object {$fileCountCam1++;$fileCount++}
Out-File is a termnating cmdlet (it doesn't ouput the object to the pipeline), so everything after it isn't getting any input from the pipeline.
See if this works better:
gci -filter *.avi |
Select-Object Name, #{Name="Age"; Expression= { (((Get-Date) - $_.CreationTime).Days) }} |
Where {$_.Age -ge $daysToKeep} |
Foreach-Object {
$_ | Out-File -filepath $logFile -append
$fileCountCam1++
$fileCount++
}