How to pipe output from foreach, in powershell - powershell

I am trying to pipe the output from a foreach loop into a format command but it does not work. The reason I think is possible is because this works.
$op = foreach ($file in (Get-ChildItem -File)) {
$file |
Get-Member |
Where-Object {$_.MemberType -eq "Method" -and $_.Definition -like "*system*" } |
Select-Object -Property Name, MemberType
}
$op | Format-List
If I can assign the whole output to a variable, and pipe the variable into another command, why does the following NOT work?
(foreach ($file in (Get-ChildItem -File)) {
$file |
Get-Member |
Where-Object {$_.MemberType -eq "Method" -and $_.Definition -like "*system*" } |
Select-Object -Property Name, MemberType
}) | Format-List
Of course I tried without parents, but ultimately I think if anything, the parents make sense. It is like $file in (Get-ChildItem -File) where it evaluates the expression in the parents and uses the result as the actual object
Is there a way to make this work?
please note that the code is not supposed to achieve anything (else) than giving an example of the mechanics

foreach does not have an output you can capture (besides the sugar you've found with variable assignment), but you can gather all the objects returned by wrapping it in a subexpression:
$(foreach ($file in Get-ChildItem -File) {
# ...
}) | Format-List
This same pattern can be used for if and switch statements as well.

Here's another way to do it, without waiting for the whole foreach to finish. It's like defining a function on the fly:
& { foreach ($file in Get-ChildItem -File) {
$file |
Get-Member |
Where-Object {$_.MemberType -eq "Method" -and $_.Definition -like "*system*" } |
Select-Object -Property Name, MemberType
}
} | format-list
By the way, $( ) can go anywhere ( ) can go, but it can enclose multiple statements separated by newlines or semicolons.
Also, you can pipe it directly:
Get-ChildItem -File |
Get-Member |
Where-Object {$_.MemberType -eq "Method" -and $_.Definition -like "*system*" } |
Select-Object -Property Name, MemberType |
Format-List

Related

How replace a character by $_ with PowerShell

I have a problem with my PowerShell script and I don't find the answer to my question.
I try to replace:
get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {
With:
get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {
My code:
$old = [regex]::Escape(' get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {')
$new = " get-vm | Where-Object {$_.IsClustered -eq `$False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {"
(Get-Content -Path c:\test\test.txt) | ForEach {$_ -Replace "$old","$new"} | Set-Content -Path c:\test\testsuccess
Before:
After:
I don't understand why the $_ is replaced by nothing. I would like to keep $_ in the output file.
UPDATE 1
I have tried to do
$old = [regex]::Escape(' get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {')
$new = " get-vm | Where-Object {`$_.IsClustered -eq `$False} | Where-Object {`$_.State -eq 'Running'} | ForEach-Object {"
(Get-Content -Path c:\test\test.txt) | ForEach {$_ -Replace "$old","$new"} | Set-Content -Path c:\test\testsuccess
But it doesn't work. The output in the testsuccess file is:
get-vm | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .IsClustered -eq $False} | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .State -eq 'Running'} | ForEach-Object {
You can use the PowerShell parser to do this along with here-strings and the .NET Replace method.
$parsedcode = [System.Management.Automation.Language.Parser]::ParseFile('c:\test\test.txt',[ref]$null,[ref]$null)
$old = #'
get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {
'#
$new = #'
get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {
'#
$parsedcode.ToString().Replace($old,$new) | Out-File c:\test\testsuccess -Force
Since we are doing static string replacements, I believe regex is just making this more complicated. The PowerShell language parser can read PowerShell code from a string or file and output it to the console or store it in memory as is. Other capabilities open up using the parser as well.
Using the here-strings (#''#) allows for special symbols in the strings to be treated literally.
The .Replace() method is a case-sensitive string replacement. It does not use regex.
Try this one:
$regex = '\sget-vm\s\|\sWhere-Object\s\{\$_\.IsClustered\s-eq\s\$False\}\s\|\sForEach-Object\s\{'
$new = " get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq 'Running'} | ForEach-Object {"
$testString = ' get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {'
$testString -replace $regex, $new
An online version of the above example can be found under tio.run.
You've to be aware to escape special characters like $ in your regex correctly ( use \).
Also, be aware to escape the simple quotes in $new correctly. I used the PowerShell quoting rules and defined $new as "formattable" string to escape ``'Running' correctly.
Update1 (based on #Ansgars comments):
Change quotation of $new to:
$new = ' get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq "Running"} | ForEach-Object {'
When enclosing the string of $new in single quotes no substitution actions are performed. In this case, you've to change the single quotes at 'Running' with double-quotes. An online example can be found here.
UPDATE 2:
Hopefully, this will be the last update. Your problem is related to the .NET defined substitution elements (which PowerShell seems to use behind the scenes):
$_ Includes the entire input string in the replacement string.
In addition PowerShell's quoting rules come into play. Let's check this example. We want to replace the word car with $_:
"car" -replace "car", "$_"
The output is empty. Why? Because of using double quotes PowerShell substitutes $_ with its actual value. Well since nothing is stored in $_ ( because it was not defined and it normally holds the actual pipeline value, but there is no pipeline included), car is replaced to an empty string.
Ok, next try, lets put $_ into single quotes:
"car" -replace "car", '$_'
The output is car. Why? Well using single quotes deactivates PowerShell's variable substitutions, and $_ is forwarded as literal to the replace operator. Now the .Net regex substitutions come into play. As stated in substitution elements $_ includes the entire input string, in our case car. So the -replace operator replaces car with car. We can see the entire input is included in this example:
"car" -replace "car", '$_ $_ $_'
Output:
car car car
So, that's the reason why we see this result in the OP:
get-vm | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .IsClustered -eq $False} | Where-Object { get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object { .State -eq 'Running'} | ForEach-Object {
Resulting from that it seems that $_ can't be escaped. The only way I could achieve the request solution for the OP is performing a double substitution (which is not the best way when thinking about performance, maybe there is a better way).
TL;DR
Solution:
$old = [regex]::Escape('get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {')
$new = ' get-vm | Where-Object {#_.IsClustered -eq $False} | Where-Object {#_.State -eq "Running"} | ForEach-Object {'
$testString = 'get-vm | Where-Object {$_.IsClustered -eq $False} | ForEach-Object {'
($testString -replace $old, $new) -replace '#', '$'
Output:
get-vm | Where-Object {$_.IsClustered -eq $False} | Where-Object {$_.State -eq "Running"} | ForEach-Object {
So I used #_ instead of $_ to deactivate substitution algorithms in general, and perform the first substitution with #_ in $new, which result in:
get-vm | Where-Object {#_.IsClustered -eq $False} | Where-Object {#_.State -eq "Running"} | ForEach-Object {
Now it is easy to substitute # with $, since $ alone is not defined in substitution elements.
Hope that solve the problem.

Write all running processes to a text file in PowerShell

The purpose of this code is to get a list of all used executables from a specific folder. After a month we will delete any exe's not on this list.
I currently get the correct results using this:
while ($true) {
foreach ($process in Get-Process | where {$_.Path -imatch 'ksv'} | select -Unique) {
$dir = $process | Get-ChildItem;
New-Object -TypeName PSObject -Property #{
'Path' = $process.Path;
} | Out-String | Add-Content -LiteralPath Z:\processList.txt
}
Get-Content Z:\processList.txt | sort | Get-Unique > Z:\uniqueprocesslist.txt
}
I'm going to get rid of the while loop as this will be eventually running as a service.
The problem with this is that it creates a huge list in processlist.txt that I would like to eliminate to save space.
I tried to come up with a better solution that scans the text file to see if the path is written already before adding the new process path. I am not sure what I am doing wrong but nothing is ever written to the text file
while ($true) {
foreach ($process in Get-Process | where {$_.Path -imatch 'ksv'} | select -Unique) {
$dir = $process | Get-ChildItem;
$progPath = New-Object -TypeName PSObject -Property #{
'Path' = $process.Path
}
$file = Get-Content "Z:\processList.txt"
$containsLine = $file | %{$_ -match $progPath}
if ($containsLine -contains $false) {
Add-Content -LiteralPath Z:\processList.txt
}
}
}
If I understand your question correctly you want to build a "recently used" list of executables in a specific directory in a file, and update that (unique) list with each run of your script.
Something like this should do that:
$listfile = 'Z:\processlist.txt'
# Build a dictionary from known paths, so that we can check for already known
# paths with an index lookup instead of a linear search over an array.
$list = #{}
if (Test-Path -LiteralPath $listfile) {
Get-Content $listfile | ForEach-Object {
$list[$_] = $true
}
}
# List processes, expand their path, then check if the path contains the
# string "ksv" and isn't already known. Append the results to the list file.
Get-Process |
Select-Object -Expand Path |
Sort-Object -Unique |
Where-Object {$_ -like '*ksv*' -and -not $list.ContainsKey($_)} |
Add-Content $listfile
Hashtable lookup and wildcard match are used for performance reasons, because they're significantly faster than linear searches in arrays and regular expression matches.
while ($true) {
$file = Get-Content "Z:\processList.txt"
$KSVPaths = Get-Process |
Where-Object {$_.Path -imatch 'ksv'} |
Select-Object -ExpandProperty Path |
Select-Object -Unique
ForEach ($KSVPath in $KSVPaths) {
if ($KSVPath -notin $file) {
Add-Content -Path $file -Value $KSVPath
}
}
}

Can't remove all the other files but those in one variable in powershell

I have a script that filters my logs, but the problem is that when I would like to delete everything else but certain files I get errors of Unrecognized escape sequence. I've been trying to split the values but it seems that nothing works. I also tried -exclude before, but didn't get it to work. It's supposed to remove all the other files but $result and $clr.
$files = #()
[xml]$photonconfig = Get-Content C:\Users\Administrator\Desktop\PhotonServer.config
$photonconfig.SelectNodes("Configuration/*") | Select-Object -Expand Name | % {
$_.Replace("xxx","")
} | ForEach {
$files+= Get-ChildItem C:\Users\Administrator\Desktop\log\log/*$_*.log |
sort -Property LastWriteTime -Descending |
Select-Object -First 3
}
$result = $files | Sort-Object LastAccessTime -Descending |
Select-Object -First 3
$clr = "PhotonCLR.log"
$all = Get-ChildItem C:\Users\Administrator\Desktop\log\log/* |
Where-Object { $_.Name -notmatch $result } |
Remove-Item
The second operand of the -match and -notmatch operators is a regular expression, not an array of file names. Use the -contains operator instead:
... | Where-Object { $result -notcontains $_.Name } | ...
On PowerShell v3 and newer you can also use the -notin operator, which feels a little more "natural" to most people:
... | Where-Object { $_.Name -notin $result } | ...
Note that for this to work you also need to expand the Name property when building $result:
$result = $files | Sort-Object LastAccessTime -Descending |
Select-Object -First 3 -Expand Name

How to manage processing a list of strings expanded from another object?

So, I've keep having trouble with tasks like this:
$fileNames = Get-ChildItem -Path . -Filter "*.*" `
| Select-Object -ExpandProperty Name
$fileNames.GetType()
I'd expect output to be string[], but it is object[].
Even worse, if I then try to manipulate the file names:
$fileNames = Get-ChildItem -Path . -Filter "*.*" `
| Select-Object -ExpandProperty Name `
| Select-Object { "$_" -replace '^.*deploy\.log\.\d\d\d\d-\d\d-\d\d\.', '' }
I'd expect again string[]. Instead I get some weird hashtable that has as a key the regex.
In both cases, its doing what I expect, just wrapping it up in objects.
What are the rules here? I can I just manipulate a list of strings?
As your last pipeline element, use ForEach-Object instead of Select-Object
$fileNames = Get-ChildItem -Path . -Filter "*.*" `
| Select-Object -ExpandProperty Name `
| ForEach-Object { "$_" -replace '^.*deploy\.log\.\d\d\d\d-\d\d-\d\d\.', '' }
You can skip the Select-Object -Expand part altogether if you wish:
$fileNames = Get-ChildItem -Filter *.* | ForEach-Object {
$_.Name -replace '^.*deploy\.log\.\d\d\d\d-\d\d-\d\d\.', ''
}
The result of your first code snippet is a generic array (hence its type says Object[]), but the elements are actually strings. If for some reason you need the type to be string[] you can simply cast the variable to that type:
[string[]]$filenames = Get-ChildItem ... | Select-Object -Expand Name
Normally that shouldn't be necessary, though.
In your second code snippet you're probably confusing Select-Object with ForEach-Object (see Mathias' answer). Instead of using a ForEach-Object loop you could also use the -replace operator directly on the expanded names:
$filenames = (Get-ChildItem ... | Select-Object -Expand Name) -replace '^.*deploy\.log\.\d{4}-\d{2}-\d{2}\.', ''

powershell converting xml object to string

I am trying to get just the value of the xml to create a directory. In the first line it will return a table with just the inner xml. However in the foreach loop it returns #{$_.Value.InnerXML=00000027627-00001} for $loanNumber. How can i convert this to just the number? is there a way i can cast it to a string and then split on the = sign? I ahve been currently trying that way and have not been succeeding. Is there a better way to just grab the value and not the junk before it?
cls
[xml]$file = Get-Content "F:\Peter Test\index.xml"
$hostdirectory = "F:\PShell Testing"
$file.ExportedResult.Docs.ExportedDoc.Doc.UdiValues.UdiValue | ? {$_.Name -eq "Loan Number"} | Select-Object {$_.Value.InnerXML} -Unique | Format-Table
foreach($loanNumber in $file.ExportedResult.Docs.ExportedDoc.Doc.UdiValues.UdiValue | ? {$_.Name -eq "Loan Number"} | Select-Object {$_.Value.InnerXML} -Unique){
Write-Host "hey"
Write-Host $loanNumber
$hostdirectoryPlusLoan = $hostdirectory + "\" + $loanNumber
if(!(test-path $hostdirectoryPlusLoan)){
New-Item $hostdirectoryPlusLoan -ItemType directory
}
}
Insert -ExpandProperty in your Select-Object command
Select-Object -ExpandProperty {$_.Value.InnerXML} -Unique
It should return just the "loannumber" string, "00000027627-00001" in this case.
And you can also do this:
$hostdirectoryPlusLoan = "$hostdirectory\$loanNumber"
instead of
$hostdirectoryPlusLoan = $hostdirectory + "\" + $loanNumber
Edit:
You use this at least twice:
$file.ExportedResult.Docs.ExportedDoc.Doc.UdiValues.UdiValue | ? {$.Name -eq "Loan Number"} | Select-Object {$.Value.InnerXML} -Unique
So what about saying
# use Select-Object <propertyName> -ExpandProperty -Unique twice to dig down into the nesting
# also, use "Where-Object" for "?"
# and mostly using a variable to hold the list of loan numbers so you don't have to filter and select exactly the same way twice.
$LoanNumberList = $file.ExportedResult.Docs.ExportedDoc.Doc.UdiValues.UdiValue | Where-Object {$_.Name -eq "Loan Number"} | Select-Object Value -ExpandProperty -Unique | Select-Object InnerXML -ExpandProperty -Unique
# assuming the above works, you can then use $LoanNumberList collection at will:
$LoanNumberList
foreach ($LoanNumber in $LoanNumberList) {
# do stuff
}