Powershell misunderstand quotation marks in a file name - powershell

Yes these files do have quotation marks in the file name.!!
I am trying to run a bat file using a powershell script. Powershell script iterate through a folder and extract files. It then pass these file names to the batch script which calls an exe to process them.
This is what the powershell script looks like,
$path = "\\RemoteMachine\Japanese\files"
$filter = "*.msg"
$count = 0
Write-Host "Parsing started...";
get-childitem -recurse -path $path -filter $filter| % {
$count++;
& "C:\Users\uname\myfolder\runCommads.bat" "$($_.FullName)" "C:\Users\uname\myfolder\output\$count.txt"
}
The input files were written using a different language. Some files' names consist of quotation marks. When PS script runs runCommand.bat, spaces that contain in the file name are misinterpreted. A Word that succeeds a space is considered as a name of a different file.
But if I run the batch file without PS script, for just one file that contains quotation marks in the file name, the expected out is given.
Could someone help me with a solution for this problem?
Thank you

This seems to be a problem of the batch file not of the powershell script.
Normally the batch parser splits the parameters at spaces.
It can be suppressed by using quotes, but if your filename itself contains quotes it will again fail.
But you could double all quotes in the filename (inside the powershell script),
then it should be possible to get the correct filenames in the batch file.
Inside the batch you need to remove the doubled quotes, but that seems to be easy.
set "filename=%~1"
setlocal EnableDelayedExpansion
set "filename=!filename:""="!"
But I suppose, that the most batch commands will fail with such filenames.

Related

Using PowerShell to pass all files of a folder as arguments to a command line program

I'm trying to create a file listing of a folder for a secure file transfer tool. This is what I do:
Get-ChildItem c:\files | % {$_.FullName} > c:\temp\list1.csv
$csv = Import-Csv C:\TEMP\list1.csv -Header Path
The output holds every file in a new line, but I need it in one line.
Required output
"C:\files\Alpha" "C:\files\Beta" "C:\files\Gamma" "C:\files\Delta"
Actual output
C:\files\Alpha
C:\files\Beta
C:\files\Gamma
C:\files\Delta
The csv file is just what came to my mind first. A variable containing the files formatted like mentioned above would be sufficient. Do you have an idea?
Edit: Thank you #Matthias R. Jessen and #WaitingForGuacamole, you gave me exactly what I wanted.
(Get-ChildItem C:\scripts -File).ForEach({'"{0}"' -f $_.FullName.Replace('"','\"')}) -join " "
However, somehow my tool (written in java) is interpreting the output as one file instead of multiple files in a line.
Below the error message:
Java : Error: The file 'C:\files\Alpha C:\files\Beta C:\files\Delta C:\files\Gamma' was not found and is excluded from the transfer.
I know, that I have to handover the paths differently when using a properties file instead of entering the command manually in PowerShell.
Is there a way on letting the output look like:
"C:\\files\Alpha" "C:\\files\Beta" "C:\\files\Gamma" "C:\\files\Delta"
To pass the file paths of all children of a specific folder to a command line program as separate arguments, just pass the results of
(Get-ChildItem -File).FullName
to the program. Example:
$files = (Get-ChildItem C:\MyFolder -File).FullName
# Expected: myprogram.exe -arg1 -arg2 C:\MyFolder\file1.txt C:\MyFolder\file2.txt ...
myprogram.exe -arg1 -arg2 $files

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

Replace all in multiple CSV - Powershell

I'm attempting to replace the hostname from inside a CSV file with a blank space " ", but I am struggling to understand how this works, as when I attempt to call $env:computername it takes it as a string rather than the variable as per below:
$var = $env:computername
[io.file]::readalltext("C:\test\test.csv").replace("$var"," ") | Out-File c:\test\test-new.csv -Encoding ascii –Force
I have tested this and can see that the script is looking for the string $var in the CSV rather than the variable. Ultimately I would like to run this against a directory of CSV files that contain different hostnames in them and output the newly edited files to a separate location also.
I removed the quotes and tested this script and it worked for my test csv file
The quotes should be expanding the variable but if not you can simply pass the environmental variable directly like so:
[io.file]::readalltext("C:\test\test.csv").replace($env:ComputerName,"") | Out-File c:\test\test-new.csv -Encoding ascii –Force

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.