Test-Path behavior in Powershell - powershell

I'm trying to write a script in powershell to batch convert video files.
The way I intend to use it is to go to some folder full of video files and run it. It uses a conversion program that can be run in "command-line mode" (named handbrake) and saves the converted files with "-android" appended to them before the file extension. For example, if I have a file named video1.avi in the folder, after running the script the folder has 2 files: video1.avi and video1-android.avi
The reason I want to do this this way is so that I can check if, for each video file, there is a converted version of it (with -android appended to the name). And if so, skip the conversion for that file.
And this is where I'm having touble. The problem is the Test-Path's behavior (the cmdlet I'm using to test if a file exists).
What happens is, if the video file has an "unusual" name (for example in my case it's video[long].avi) Test-Path always returns False if you try to check if that file exists.
An easy way for you to test this is for example to do this:
Go to an empty folder,
run notepad to create a file with "[" in its name:
&notepad test[x].txt
Save the file
then do this:
Get-ChildItem | ForEach-Object {Test-Path $_.FullName}
It does not return true! It should right? Well it doesn't if the file has "[" in its name (I didn't check for any other special characters)
I've realized that if you escape the "[" and "]" it works
Test-Path 'test`[x`].txt'
returns true.
How can I go around this issue? I want to be able to: given a BaseName of a file, append it "-android.avi" and check if a file with that name exists.
Thanks,
Rui

Many PowerShell cmdlets have Path parameters that support wildcarding. As you have observed, in PowerShell not only is * a wildcard but [ and ] are also considered wildcard characters. You can read more about this in the help topic about_Wildcards.
For your issue, if you don't need wildcarding then I would recommend using the -LiteralPath parameter. This parameter doesn't support wildcarding and accepts [ and ] as literal path characters e.g.:
Get-ChildItem | ForEach {Test-Path -LiteralPath `
"$([io.path]::ChangeExtension($_.FullName,'avi'))"}
FYI, the reason piping the output of Get-ChildItem directly into Test-Path works is because the LiteralPath parameter has an alias "PSPath" that maps to the PSPath property on the FileInfo object output by Get-ChildItem. That property gets bound to the LiteralPath (er PSPath) parameter "by property name".

dir | % {test-path "$($_.Name)-android.avi"}

Related

Converting batch commands with Powershell that finds files and create parameters

In a cmd window I search for a specific file then pass it into an executable. I want to redo this using PowerShell, with similar constraints on being on a single line or two independent and separate lines, but being new I can't figure it out.
cmd /r dir /s /b RunnerUnitTest.dll | findstr /r bin\\ > tests_list.txt
cmd /r for /f %f in (tests_list.txt) do vstest.console.exe "%f"
The first command looks for a file, it finds two:
RunnerUnitTest\bin\Debug\RunnerUnitTest.dll
RunnerUnitTest\obj\Debug\RunnerUnitTest.dll
then it narrows it down to one (bin in the path) and sticks it into a file:
RunnerUnitTest\bin\Debug\RunnerUnitTest.dll
The second line takes this output and passes it into an executable as a parameter.
This is for a gitlab runner, btw
You can do something like this without an intermediate file, since Get-ChildItem will return a FileInfo object for files, and DirectoryInfo objects for directories (both of which are derived from FileSystemInfo):
Get-ChildItem -Recurse bin\Debug\RunnerUnitTest.dll | Select-Object -First 1 | Foreach-Object {
vstest.console.exe $_.FullName
}
What this will do is recursively search for all files found named RunnerUnitTest.dll with parent folders Debug and bin, and then for each file returned (I've added a Select-Object -First 1 to ensure we only get one assembly back but this is optional) pass the full path to the assembly to the vstest.console.exe program.
The way it's written above, the assembly under the obj folder won't be returned at all for execution consideration.
As an FYI, piping with | works much in the same way as it does with other shells like Bash or cmd, but it supports passing around complex objects instead of strings. As a general rule, you can pipe any output on the output stream to other cmdlets or save the output to variables. Redirection operators work similarly as well.

How to create a file in the directory in PowerShell

My dirctory is D:\\Main. I want to create a file named file.js in the directory only i.e. D:\\Main. I use the code
type nul > file.js. It creates the file but also displayes an error message saying that nul folder wasn't found. Am I doing something wrong? How do I improve the code to stop the error message?
What you attempted to use is valid for CMD.EXE, but in PowerShell, type is an alias for Get-Content, which expects a valid device or file. However, the null device is not available in PowerShell, although a variable $null will return the desired value. You should instead use
New-Item -Path . -Name "file.js" -Value $null
or
Set-Content -Path .\file.js -Value $null
to create an empty file.
I didn't get a satisfactory answer so I searched the command list for PowerShell on Microsoft's website.
Use
Set-Content -path D:\\Main\file.js
Then it will ask for values *(e.g. [-Value0], [-Value1], ...)." Values are what what you want to type in the file e.g. [-Value0] will contain what will be in the first line of the code of file.js. Pressing enter will move to next line i.e. [-Value1]. Pressing enter without typing anything in a value will exit taking values and save the file.
The above command can be shortened like:
sc for Set-Content & . for D:\\Main because it is your default directory. You can also skip the -path term. So the new shortened command will be:
sc .\file.js

How to do a copy /b in a powershell script to insert a BOM marker, but as a batch for files that match a filter and changes the ext on output?

REASONS WHY THIS IS NOT A DUPLICATE
Since 3 people have already voted to close, I guess I should explain why this question is not a duplicate:
I cannot use cat or >> as these mess up the encoding of the files, which are UTF8 on input and need to be UTF8-BOM on output.
The linked question does not show how to loop through all files that match a given pattern in a directory, and concatenate a single file to each of the matching files on output, plus give the new file a different extension.
Using Set-Content is not Powershell 6 future-proof, since Set-Content will NOT add a BOM marker. In Powershell 5 and below, it sometimes adds a BOM marker and sometimes not, depending on the configuration settings of the executing user. See 'quick note on encoding' at the end of this article.
So in conclusion I am looking for a solution that uses copy (hence the question title) and does NOT use Cat or Set-Content.
I need to loop through certain files in a given directory and run the following on each file:
copy /b BOMMarker.txt+InputFile.dat OutputFile.txt
This inserts the contents of the BOMMarker.txt file at the start of the InputFile.dat and writes the output to OutputFile.txt
I found this question which explains how I can loop through the folder to load each file into Powershell, but how do I apply the "copy /b" command so that I can get the BOM marker at the start of each file?
EDIT
The comment from Jeroen indicates I can just do Set-Content on the output file, as Powershell will automatically add the BOM at the start.
But I also need to change the extension. So the output filename needs to be the same as the input filename, just with a changed extension (from .dat to .txt) and including the BOM.
I am guessing I can use Path.ChangeExtension somehow to do this, but not sure how to combine that with also adding the BOM.
EDIT - for Bounty
The example answer I posted does not work in all environments I tested it, and I do not know why (possibly different default Powershell setttings) but also, it is not future proof since Powershell 6 will not output BOM by default.
From the given directory, I need to process all files that match the filter (DIL_BG_TXN*.dat).
For each of those files, I need to copy it with a BOM at the start but the resultant new file needs to be the same name but with the extension .txt instead of .dat.
This solutions uses streams, that reliably read and write as-is:
$bomStream = [IO.File]::OpenRead('BOMMarker.txt')
$location = "" # set this to the folder location
$items = Get-ChildItem -Path $location -Filter DIL_BG_TXN*.dat
foreach ($item in $items) {
$sourceStream = [IO.File]::OpenRead($item.FullName)
$targetStream = [IO.File]::OpenWrite([IO.Path]::ChangeExtension($item.FullName, '.txt'))
$bomStream.CopyTo($targetStream)
$sourceStream.CopyTo($targetStream)
$targetStream.Flush()
$targetStream.Close()
$sourceStream.Close()
$bomStream.Position = 0
}
$bomStream.Close()
Of course please write the absolute path of BOMMarker.txt (1st line) according to its location.
This finally worked:
$Location = "C:\Code\Bulgaria_Test"
$items = Get-ChildItem -Path $Location -Filter DIL_BG_TXN*.dat
ForEach ($item in $items) {
Write-Host "Processing file - " $item
cmd /c copy /b BOMMarker.txt+$item ($item.BaseName + '.txt')
}
Description:
Set the directory location where all the .dat files are.
Load only those files that match the filter into the array $items.
Loop through each $item in the array.
With each $item, call cmd shell with the copy /b command and concatenate the bom marker file with the $item file and write the result to the basename of $item plus the new extension.

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. {...}.

PowerShell: read lines from text file, construct source and destination file names, then copy files

I'm a PowerShell novice, and I'd love to be able to script this. I have a text file where each line is part of a file name without the path or extension. I'd like a one-liner that loops through each line of the file (with a gc - Get-Content, right?), takes the content of the line, constructs the source path (the network drive and extension are static), constructs a destination path, and then copies each file. My file content is like this:
12345678
98765432
32145698
12365782
And my source folder is a UNC path like this:
\\server\share
And my destination folder is:
c:\temp\files
I would like to do the equivalent of this DOS command, using $_ as the text from each line of the file:
copy \\server\share\$_.ext c:\temp\files\$_.ext
I'm pretty sure I can use gc and $_ to access each line of the file, and that I need to use cp to copy the files, but I'm having trouble constructing the source and destination file names.
Try the following
gc theFileName |
%{ "{0}.ext" -f $_ } |
%{ copy "\\server\share\$_" "c:\temp\files\$_" }
It can actually be done on one line but it looks better formmated as multiple lines for this answer :)
Copy-Item can take a script block directly in this case so the Foreach-Object stages are unnecessary:
gc theFileName | cpi -l {"\\server\share\$_.exe"} c:\temp\files -whatif
Remove the -WhatIf parameter once you're satisfied it works. The -l is short for -LiteralPath which helps PowerShell determine which parameterset is in use. Also better to use literal path here so that wildcard characters don't get globbed (unless you want that - if so then use -path).
Essentially pipeline bound parameters can be specified via scriptblocks and PowerShell will attempt to resolve the result of the scriptblock to the type expected by the pipeline bound parameter.