Run a program in a ForEach loop - powershell

I'm trying to get this simple PowerShell script working, but I think something is fundamentally wrong. ;-)
ls | ForEach { "C:\Working\tools\custom-tool.exe" $_ }
I basically want to get files in a directory, and pass them one by one as arguments to the custom tool.

If you still need quotes around the command path (say, if you've got spaces), just do it like this:
ls | % { &"C:\Working\tools\custom-tool.exe" $_.FullName }
Notice the use of & before the string to force PowerShell to interpret it as a command and not a string.

ls | %{C:\Working\tools\custom-tool.exe $_}
As each object comes down the pipeline the tool will be run against it. Putting quotes around the command string causes it to be... a string! The local variable "$_" it then likely doesn't know what to do with so pukes with an error.

I'm betting your tool needs the full path. The $_ is each file object that comes through the pipeline. You likely need to use an expression like this:
ls | %{C:\Working\tools\custom-tool.exe $_.fullname}

Both Jeffrery Hicks and slipsec are correct. Yank the double quotes off.
$_ or $_.fullname worked in my test script (below). YMMV with your custom-tool.
gci | % { c:\windows\notepad.exe $_.fullname }
or
gci | % { c:\windows\notepad.exe $_ }

Related

Perform a command adding an extension to list of files

I have a Windows executable munge.exe which takes an input .dat file and produces a file with the -o parameter for the output filename. I want to append (not replace) an .out extension to the output, so I call it like this for foo.dat and bar.dat:
munge foo.dat -o foo.dat.out
munge bar.dat -o bar.dat.out
I just got a large directory of various files, among them many .dat files. Many of them have spaces in their names. It's so much work to munge them manually. I thought PowerShell could help. As a newbie, I did a few searches and came up with this, making liberal use of aliases:
dir *.dat | %{munge $_.name -o $_.name+".out"}
Needless to say, it didn't work. I tried to find out where I went wrong, so I just tried to echo the commands to see what was going on. (The wonderful -whatif as you might expect doesn't help me, because of course it doesn't work with script blocks. Of course. Wonderful)
dir *.dat | %{echo munge $_.name -o $_.name+".out"}
No go; it thinks that -o is a parameter of echo. So let's quote the thing!
dir *.dat | %{echo "munge $_.name -o $_.name+.out"}
Oops I'm getting .name and + in the interpolation. Maybe if I use the ${…} format and leave out the + since it's interpolation.
dir *.dat | %{echo "munge ${_.name} -o ${_.name}+.out"}
Um … no. That gives me munge -o .out.
(sigh) Looks like I'll have to learn all the Powershell tricks just to do basic things (as I had to do with Bash). Nothing is simple I guess.
What is the way I'm supposed to do it?
The relevant documentation in this case is about Operator Precendence.
You need to use the Grouping operator ( ) so that the string concatenation between $_.Name and .out is resolved first in the expression:
Get-ChildItem *.dat | ForEach-Object {
munge $_.name -o ($_.name + ".out")
}
As for the second example, where you attempt to echo the result, in this case, you need the Subexpression operator $( ) so that the string interpolation is resolved first :)
Get-ChildItem *.dat | ForEach-Object {
echo "munge $($_.name) -o $($_.name + ".out")"
}
Lastly, regarding -WhatIf, echo (Write-Output) is not a cmdlet that supports ShouldProcess, hence no -WhatIf switch is available for it.

Nested pipeline variable in powershell

I have the following directory tree composed of a root directory containing 10 subdirectories, and 1 file in each subdirectory.
root/
dir1/
file
dir2/
file
...
dir10/
file
I would like to edit the content of the files recursively, replacing a string "str1" by "str2". I issued the following command in powershell:
Get-ChildItem -Directory | foreach {(get-content $_/file) -replace "str1", "str2" | set-content $_/file}
And it worked like a charm, but I still do not understand how. I use a pipeline in the foreach loop, but the following call to $_ still refers to the pipeline outside the foreach loop. Why is it so?
I don't think your command did work, is the problem.
The -Directory switch of Get-ChildItem makes it only return directories, not files. If you want to return files, use the -File switch.
Next up, if you have a list of items from Get-ChildItem, those give you a System.IO.FileSystemInfo object. We can provide those directly to the Get-Content command to read the file into a string.
From a string, you can call any of the general operators PowerShell offers, including the string replace operator, -Replace. The output of this can be piped over to Set-Content and used to update or append content to an existing file.
Get-ChildItem | foreach {
(get-content $_) -replace "str1", "str2" | Set-Content $_.FullName
}
Note the only real change here is that I removed -Directory from Get-ChildItem, and then fixed the syntax on the $PSItem (the official name for the present variable in a forEach loop, often written as $_).
The reason you can use the syntax I showed is that forEach-object gives you that special $_ or $PSitem variable to use to reference $this in a collection.
The special variable $_ is not unique for the foreach loop.
It is a placeholder for the object being passed through the pipe line.
This article: POWERSHELL POWER LESSON: THIS IS NO ORDINARY VARIABLE goes into more detail about the $_ variable.
Both $_ and the | are inside the foreach loop in the curly braces { } or script block.

How to display the file a match was found in using get-content and select-string one liner

I am attempting to search a directory of perl scripts and compile a list of all the other perl scripts executed from those files(intentionally trying to do this through Powershell). A simplistic dependency mapper, more or less.
With the below line of code I get output of every line where a reference to a perl file is found, but what I really need is same output AND the file in which each match was found.
Get-Content -Path "*.pl" | Select-String -Pattern '\w+\.pl' | foreach {Write-Host "$_"}
I have succeeded using some more complicated code but I think I can simplify it and accomplish most of the work through a couple lines of code(The code above accomplishes half of that).
Running this on a windows 10 machine powershell v5.1
I do things like this all the time. You don't need to use get-content.
ls -r *.pl | Select-String \w+\.pl
file.pl:1:file2.pl
You don't need to use ls or Get-ChildItem either; Select-String can take a path parameter:
Select-String -Pattern '\w+\.pl' -Path *.pl
which shortens to this in the shell:
sls \w+\.pl *.pl
(if your regex is more complex it might need spaces around it).
For the foreach {write-host part, you're writing a lot of code to turn useful objects back into less-useful strings, and forcibly writing them to the host instead of the standard output stream. You can pick out the data you want with:
sls \w+\.pl *.pl | select filename, {$_.matches[0]}
which will keep them as objects with properties, but render by default as a table.

Powershell - piping to external program as arguments

I am dabbling with Powershell and attempting to replace the old console 'for' command. For instance, to encode a folder of *.WAV files using "FLAC.EXE" which is located on the path:
(Get-ChildItem)|Where-Object{$_.extension -eq ".wav"}|flac "$_.Name"
However I get a result where clearly Flac is not receiving the file name and only the literal string "$_.Name".
This is a very obvious problem I am sure, but I am still feeling my way along at this stage.
Try it like this:
Get-ChildItem *.wav | Foreach-Object {flac $_.FullName}
The automatic variable $_ is typically only valid inside the context of a scriptblock that is part of a pipeline e.g. {...}.

How to enter a multi-line command

Is it possible to split a PowerShell command line over multiple lines?
In Visual Basic I can use the underscore (_) to continue the command in the next line.
You can use a space followed by the grave accent (backtick):
Get-ChildItem -Recurse `
-Filter *.jpg `
| Select LastWriteTime
However, this is only ever necessary in such cases as shown above. Usually you get automatic line continuation when a command cannot syntactically be complete at that point. This includes starting a new pipeline element:
Get-ChildItem |
Select Name,Length
will work without problems since after the | the command cannot be complete since it's missing another pipeline element. Also opening curly braces or any other kind of parentheses will allow line continuation directly:
$x=1..5
$x[
0,3
] | % {
"Number: $_"
}
Similar to the | a comma will also work in some contexts:
1,
2
Keep in mind, though, similar to JavaScript's Automatic Semicolon Insertion, there are some things that are similarly broken because the line break occurs at a point where it is preceded by a valid statement:
return
5
will not work.
Finally, strings (in all varieties) may also extend beyond a single line:
'Foo
bar'
They include the line breaks within the string, then.
I just found out that there must not be any character between the back tick and the line break. Even whitespace will cause the command to not work.
In most C-like languages I am deliberate about placing my braces where I think they make the code easiest to read.
PowerShell's parser recognizes when a statement clearly isn't complete, and looks to the next line. For example, imagine a cmdlet that takes an optional script block parameter:
Get-Foo { ............ }
if the script block is very long, you might want to write:
Get-Foo
{
...............
...............
...............
}
But this won't work: the parser will see two statements. The first is Get-Foo and the second is a script block. Instead, I write:
Get-Foo {
...............
...............
...............
}
I could use the line-continuation character (`) but that makes for hard-to-read code, and invites bugs.
Because this case requires the open brace to be on the previous line, I follow that pattern everywhere:
if (condition) {
.....
}
Note that if statements require a script block in the language grammar, so the parser will look on the next line for the script block, but for consistency, I keep the open brace on the same line.
Simlarly, in the case of long pipelines, I break after the pipe character (|):
$project.Items |
? { $_.Key -eq "ProjectFile" } |
% { $_.Value } |
% { $_.EvaluatedInclude } |
% {
.........
}
To expand on cristobalito's answer:
I assume you're talking about on the command-line - if it's in a script, then a new-line >acts as a command delimiter.
On the command line, use a semi-colon ';'
For example:
Sign a PowerShell script on the command-line. No line breaks.
powershell -Command "&{$cert=Get-ChildItem –Path cert:\CurrentUser\my -codeSigningCert ; Set-AuthenticodeSignature -filepath Z:\test.ps1 -Cert $cert}
In PowerShell and PowerShell ISE, it is also possible to use Shift + Enter at the end of each line for multiline editing (instead of standard backtick `).
Just add a corner case here. It might save you 5 minutes. If you use a chain of actions, you need to put "." at the end of line, leave a space followed by the "`" (backtick). I found this out the hard way.
$yourString = "HELLO world! POWERSHELL!". `
Replace("HELLO", "Hello"). `
Replace("POWERSHELL", "Powershell")
Just use ` character to separate command on multiline
If you are trying to separate strings into multiple lines, you can use the "+". For example:
$header = "Make," +
"ComputerName," +
"Model," +
"Windows Version"
Will look just like:
$header = "Make,ComputerName,Model,Windows Version"
I started by doing
if ($true) {
"you can write multiple lines here, and the command doesn't run untill you close the bracket"
"like this"
}
Recently found out I could just
&{
get-date
"more stuff"
}
I assume you're talking about on the command-line - if it's in a script, then a new-line acts as a command delimiter.
On the command line, use a semi-colon ';'
Use a semi-colon ; to separate command
Replace double backslash \\ on any backslashes \.
Use "' for passing safe address to switch command like "'PATH'".
This ps1 command install locale pfx certificate.
powershell -Command "$pfxPassword = ConvertTo-SecureString -String "12345678" -Force -AsPlainText ; Import-PfxCertificate -FilePath "'C:\\Program Files\\VpnManagement\\resources\\assets\\cert\\localhost.pfx'" Cert:\\LocalMachine\\My -Password $pfxPassword ; Import-PfxCertificate -FilePath "'C:\\Program Files\\VpnManagement\\resources\\assets\\cert\\localhost.pfx'" Cert:\\LocalMachine\\Root -Password $pfxPassword"
This is an old post, so here's the modern method.
If you're not using legacy powershell, the cleanest way to continue lines is the pipe at the start of the line.
Note: The command doesn't break with some lines commented out. This is great on the command line.
> Get-ChildItem -path 'c:\' -Depth 1
| Sort-Object LastWriteTime
# | Sort-Object Length -Descending
| Select-Object -First 3 -Skip 3
| Foreach-Object {
$_.Name, $_.Length | Join-String -Separator ' = '
}
output:
explorer.exe = 4826160
procexp.old.exe = 2925760
RtlExUpd.dll = 2839488
Windows Powershell ( Version < 6 )
Unfortunately windows powershell does not support it. A bunch of alternatives are linked above. You can remove the backtick completely: 2017/07/bye-bye-backtick-natural-line
There's sooo many ways to continue a line in powershell, with pipes, brackets, parentheses, operators, dots, even with a comma. Here's a blog about it: https://get-powershellblog.blogspot.com/2017/07/bye-bye-backtick-natural-line.html
You can continue right after statements like foreach and if as well.
$scriptBlock = [Scriptblock]::Create(#'
echo 'before'
ipconfig /all
echo 'after'
'#)
Invoke-Command -ComputerName AD01 -ScriptBlock $scriptBlock
source
don't use backquote
In windows terminal (powershell profile) I can simply click Shift-Enter works fine for me.
PS C:\xxx2021> Get-ChildItem -Include *remote* -Recurse |
>> Sort-Object -Property LastWriteTime -Descending |
>> Select-Object LastWriteTime, Name -First 25
LastWriteTime Name
------------- ----
12/5/2021 5:04:02 PM remote-control-car-BatteryPack-Number-2021-12-03.pdf
PS C:\xxx2021>enter code here