powershell : command does not work when called inside a loop - powershell

The following command does work in a powershell console
Restore-SvnRepository D:\temp\Backup\foo.vsvnbak
(Restore-SvnRepository is a command that comes with visualsvn, it expects a path or unc to a file to be restored as parameter)
As I need to execute this command for a large number of files (>500) I embedded it inside a powershell loop but then it doesn't work
$fileDirectory = "D:\temp\Backup"
$files = Get-ChildItem $fileDirectory -Filter "*.vsvnbak"
foreach($file in Get-ChildItem $fileDirectory)
{
$filePath = $fileDirectory + "\" + $file;
# escape string for spaces
$fichier = $('"' + $filepath + '"')
# write progress status
"processing file " + $fichier
# command call
Restore-SvnRepository $fichier
}
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
I don't understand why this does not work. The loop and filename looks good but when executed, each command throws the following error message
Restore-SvnRepository : Parameter 'BackupPath' should be an absolute or UNC path to the repository
backup file you would like to restore: Invalid method Parameter(s) (0x8004102F)
Could you help me?
EDIT
It looks like that I was confused by Get-ChildItem that return a System.IO.FileSystemInfo rather than a string.
I didn't notice because of a implicit call to the ToString() when writing to the console that made me think I was dealing with string (rather than FSI)
The following code works
$fileDirectory = "D:\temp\Backup\"
$files = Get-ChildItem $fileDirectory -Filter "*.vsvnbak"
foreach($file in $files)
{
# $file is an instance of System.IO.FileSystemInfo,
# which contains a FullName property that provides the full path to the file.
$filePath = $file.FullName
Restore-SvnRepository -BackupPath $filePath
}

$file isn't a string, it's an object containing file data.
You can simplify your code as follows:
$fileDirectory = "D:\temp\Backup"
$files = Get-ChildItem $fileDirectory -Filter "*.vsvnbak"
foreach($file in $files)
{
# $file is an instance of System.IO.FileSystemInfo,
# which contains a FullName property that provides the full path to the file.
$filePath = $file.FullName
# ... your code here ...
}

Related

Powershell: ForEach Copy-Item doesn't rename properly when retrieving data from array

I am pretty new to PowerShell and I need some help. I have a .bat file that I want to copy as many times as there are usernames in my array and then also rename at the same time. This is because the code in the .bat file remains the same, but for it to work on the client PC it has to have the username as a prefix in the filename.
This is the code that I have tried:
$usernames = Import-Csv C:\Users\Admin\Desktop\usernames.csv
$file = Get-ChildItem -Path 'C:\Users\Admin\Desktop\generatedbat\' -Recurse
foreach ($username in $usernames)
{
ForEach-Object {Copy-Item $file.FullName ('C:\Users\Admin\Desktop\generatedbat\' + $username + $File.BaseName + ".bat")}
}
This copies everything and it kind of works but I have one problem.
Instead of having this filename: JohnR-VPNNEW_up.bat
I get this: #{Username=JohnR}-VPNNEW_up.bat
Any help? Thanks!
So you have one .bat file C:\Users\Admin\Desktop\generatedbat\VPNNEW_up.bat you want to copy to the same directory with new names taken from the usernames.csv --> Username column.
Then try
# get an array of just the UserNames column in the csv file
$usernames = (Import-Csv -Path 'C:\Users\Admin\Desktop\usernames.csv').Username
# get the file as object so you can use its properties
$originalFile = Get-Item -Path 'C:\Users\Admin\Desktop\generatedbat\VPNNEW_up.bat'
foreach ($username in $usernames) {
$targetFile = Join-Path -Path $originalFile.DirectoryName -ChildPath ('{0}-{1}' -f $username, $originalFile.Name)
$originalFile | Copy-Item -Destination $targetFile -WhatIf
}
I have added switch -WhatIf so you can first test this out. If what is displayed in the console window looks OK, then remove that -WhatIf safety switch and run the code again so the file is actually copied
I kept the code the same but instead of using a .csv file I just used a .txt file and it worked perfectly.

Retrieving file information of specific date using powershell

I have a base folder as:
D:\St\Retail\AMS\AMS\FTP-FromClient\AMS
It contains various folders of dates:
2022-04-01
2022-04-02
...
...
2022-02-02
2021-05-05
2019-04-12
And each of these folders contains own files inside the folder. So, I want to retrieve all the filename inside the folder if it has 2022-04. So if the folder has '2022-04' as the base name ,I need to retreive all the file inside the folder like '2022-04-01','2022-04-02','2022-04-03'. The way I tried is:
cls
$folerPath = 'D:\St\Retail\AMS\AMS\FTP-FromClient\AMS'
$files = Get-ChildItem $folerPath
[System.Collections.ArrayList]$data = #()
foreach ($f in $files) {
$a = Get-ChildItem $f.FullName
foreach ($inner in $a) {
echo $inner.FullName
$outfile = $inner.FullName -match '*2022-04*'
$datepart = $inner.FullName.split('\')[-1]
if ($outfile) {
$data.add($datepart + '\' + $inner.Name.Trim())
}
}
}
My final $data may contains like this:
2022-04-01/abc.txt
2022-04-02/cde.pdf
2022-04-03/e.xls
You can do this by first collecting the directories you want to explore and then loop over these to get the files inside.
Using a calculated property you can output in whatever format you like:
$folderPath = 'D:\St\Retail\AMS\AMS\FTP-FromClient\AMS'
$data = Get-ChildItem -Path $folderPath -Filter '2022-04*' -Directory | ForEach-Object {
$dir = $_.Name
(Get-ChildItem -Path $_.FullName -File |
Select-Object #{Name = 'FolderFile'; Expression = {'{0}\{1}' -f $dir, $_.Name}}).FolderFile
}
After this, $data would be a string array with this content:
2022-04-01\abc.txt
2022-04-02\cde.pdf
2022-04-03\e.xls
By using wildcards for both directory and file name, you only need a single Get-ChildItem call:
$folderPath = 'D:\St\Retail\AMS\AMS\FTP-FromClient\AMS'
$folderDate = '2022-04'
[array] $data = Get-ChildItem "$folderPath/$folderDate*/*" -File | ForEach-Object{
# Join-Path's implicit output will be captured as an array in $data.
Join-Path $_.Directory.Name $_.Name
}
$data will be an array of file paths like this:
2022-04-01\abc.txt
2022-04-02\cde.pdf
2022-04-03\e.xls
Notes:
[array] $data makes sure that the variable always contains an array. Otherwise PowerShell would output a single string value when only a single file is found. This could cause problems, e. g. when you want to iterate over $data by index, you would iterate over the characters of the single string instead.
To make this answer platform-independent I'm using forward slashes in the Get-ChildItem call which work as path separators under both Windows and *nix platforms.
Join-Path is used to make sure the output paths use the expected default path separator (either / or \) of the platform.

Converting a line of cmd to powershell

EDIT2: Final code below
I need help on converting some codes as I am very new to mkvmerge, powershell and command prompt.
The CMD code is from https://github.com/Serede/mkvtoolnix-batch/blob/master/mkvtoolnix-batch.bat
for %%f in (*.mkv) do %mkvmerge% #options.json -o "mkvmerge_out/%%f" "%%f"
What I've managed so far
$SourceFolder = "C:\tmp" #In my actual code, this is done using folder browser
$SourceFiles = Get-ChildItem -LiteralPath $SourceFolder -File -Include *.mkv
$SourceFiles | foreach
{
start-process "F:\Desktop\#progs\mkvtoolnix\mkvmerge.exe"
}
I'd be grateful for any help as I'm having trouble understanding and converting while learning both sides. Thank you very much.
**EDIT 2:**Here's my final working code.
Function Get-Folder($initialDirectory) {
#Prompt to choose source folder
[void] [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
$FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog
$FolderBrowserDialog.Description = 'Choose the video folder'
$FolderBrowserDialog.RootFolder = 'MyComputer'
if ($initialDirectory) { $FolderBrowserDialog.SelectedPath = $initialDirectory }
[void] $FolderBrowserDialog.ShowDialog()
return $FolderBrowserDialog.SelectedPath
}
Function ExitMessage
{
#endregion Function output
Write-Host "`nOperation complete";
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');
Exit;
}
($SourceFolder = Get-Folder | select )
#Check for output folder and create if unavailable
$TestFile = "$SourceFolder" + "\mkvmerge_out"
if ((Test-Path -LiteralPath $TestFile) -like "False")
{
new-item -Path $SourceFolder -name "mkvmerge_out" -type directory
Write-Host 'Folder created';
}
#Checking for the presence of a Json file
$TestFile = (Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.json)
if ($TestFile.count -eq 0)
{
Write-Host 'json file not found';
ExitMessage;
}
$TestFile = "$SourceFolder" + "\$TestFile"
#Getting the total number of files and start timer.
[Int] $TotalFiles = 0;
[Int] $FilesDone = 0;
$TotalFiles = (Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.mkv).count
$PercentFiles = 0;
$Time = [System.Diagnostics.Stopwatch]::StartNew()
#Start mkvmerge process with progress bar
$mkvmergeExe = 'F:\Desktop\#progs\mkvtoolnix\mkvmerge.exe'
$JsonFile = "$TestFile" # alternatively, use Join-Path
Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.mkv | ForEach-Object {
$PercentFiles = [math]::truncate(($FilesDone/$TotalFiles)*100)
Write-Progress -Activity mkvmerge -Status ("{0}% Completed; {1}/{2} done; Time Elapsed: {3:d2}:{4:d2}:{5:d2}" -f $PercentFiles, $FilesDone, $TotalFiles, $Time.Elapsed.Hours, $Time.Elapsed.minutes, $Time.Elapsed.seconds) -PercentComplete $PercentFiles;
Write-Host "Processing $_"
$f = $_.FullName
$of = "$SourceFolder\mkvmerge_out\$($_.Name)"
& $mkvmergeExe -q `#$JsonFile -o $of $f
$FilesDone++
}
Remove-Item -LiteralPath $JsonFile #Remove this line if you want to keep the Json file
$PercentFiles = [math]::truncate(($FilesDone/$TotalFiles)*100)
Write-Progress -Activity mkvmerge -Status ("{0}% Completed; {1}/{2} done; Time Elapsed: {3:d2}:{4:d2}:{5:d2}" -f $PercentFiles, $FilesDone, $TotalFiles, $Time.Elapsed.Hours, $Time.Elapsed.minutes, $Time.Elapsed.seconds) -PercentComplete $PercentFiles;
ExitMessage;
$mkvmergeExe = 'F:\Desktop\#progs\mkvtoolnix\mkvmerge.exe'
$optionsFile = "$SourceFolder\options.json" # alternatively, use Join-Path
Get-ChildItem -LiteralPath $SourceFolder -File -Filter *.mkv | ForEach-Object {
$f = $_.FullName
$of = "$SourceFolder\mkvmerge_out\$($_.Name)"
& $mkvmergeExe `#$optionsFile -o $of $f
}
Note that your cmd code assumes that it's operating in the current directory, while your PowerShell code passes a directory explicitly via $SourceFolder; therefore, the options.json file must be looked for in $SourceFolder and too, and the output file path passed to -o must be prefixed with $SourceFolder too which is achieved via expandable strings ("...") .
The main points to consider:
for %%f in (*.mkv) has no direct counterpart in PowerShell; you correctly used Get-ChildItem instead, to get a list of matching files, which are returned as System.IO.FileInfo instances.
However, -Include won't work as intended in the absence of -Recurse (unless you append \* - see this GitHub issue; -Filter does, and is also the faster method, but it has its limitations and legacy quirks (see this answer).
While PowerShell too allows you to execute commands whose names or paths are stored in a variable (or specified as a quoted string literal), you then need &, the call operator, to invoke it, for syntactic reasons.
Inside a script block ({ ... }) passed to the ForEach-Object cmdlet, automatic variable $_ represents the pipeline input object at hand.
$_.FullName ensures that the System.IO.FileInfo input instances are represented by their full path when used in a string context.
This extra step is no longer necessary in PowerShell [Core] 6+, where System.IO.FileInfo instances thankfully always stringify as their full paths.
The # character is preceded by ` (backtick), PowerShell's escape character, because # - unlike in cmd - is a metacharacter, i.e. a character with special syntactic meaning. `# ensures that the # is treated verbatim, and therefore passed through to mkvmerge.
Alternatively, you could have quoted the argument instead of escaping just the #: "#$optionsFile"
See this answer for background information.
You generally do not need to enclose arguments in "..." in PowerShell, even if they contain spaces or other metacharacters.

powershell wrong results for file length

I have a file with length of 309247 bytes as this image:
If I run this powershell script:
(Get-Item 'C:\TestFile.082').length
309247
But if run the same script but a $file variable it returns this:
$fpath = Get-Item $file.FullName
$fpath.length
309800
# or
(Get-Item $file.FullName).length
309800
# or
$file.length
309800
NOTE $file is a variable into a loop for all files contained in a directory
Why if I'm referencing the same file the results are different?
How can I do to get the real value (309247)?
This is my Full Code
$filesToFtp = Get-Item $pathToFtp -Exclude "*.PreARM"
foreach($file in $filesToFtp)
{
$fpath = Get-Item $file.FullName
$fpath.Length
$FileNode = $oXMLDocumentFiles.CreateElement("element","curtmpremotelxml_files","")
$nodoXML = "<versionnum>"+$versplain+"</versionnum>"+
"<filename>"+$file.Name.ToUpper()+"</filename>"+
"<filesize>"+$file.length+".00</filesize>"+
$FileNode.InnerXml = $nodoXML
$oXMLDocumentFiles.DocumentElement.AppendChild($FileNode)
}
It looks like you aren't using the cmdlet you think you are:
foreach( $file in (Get-Item 'c:\')) { ($file.FullName).Length }
is a condensed version of your variable to fill $fPath, but what it returns is a System.IO.FileSystemInfo object, of which there is a member called FullPath, a string.
What I assume you want it
foreach( $file in (Get-ChildItem 'c:\')) { ($file.FullName).Length }
which would return an Array of filepaths of the child items (or files within the folder)
This would also let you clean up a few things
$currentFilePath = $file.FullName
$fLength = $currentFilePath.Length

Parse directory listing and pass to another script?

I am trying to write a PowerShell script that will loop through a directory in C:\ drive and parse the filenames with the file extension to another script to use.
Basically, the output of the directory listing should be accessible to be parsed to another script one by one. The script is a compiling script which expects an argument (parameter) to be parsed to it in order to compile the specific module (filename).
Code:
Clear-Host $Path = "C:\SandBox\"
Get-ChildItem $Path -recurse -force | ForEach { If ($_.extension -eq ".cob")
{
Write-Host $_.fullname
}
}
If ($_.extension -eq ".pco")
{
Write-Host $_.fullname }
}
You don't need to parse the output as text, that's deprecated.
Here's something that might work for you:
# getmyfiles.ps1
Param( [string])$Path = Get-Location )
dir $Path -Recurse -Force | where {
$_.Extension -in #('.cob', '.pco')
}
# this is another script that calls the above
. getmyfile.ps1 -Path c:\sandbox | foreach-object {
# $_ is a file object. I'm just printing its full path but u can do other stuff eith it
Write-host $_.Fullname
}
Clear-Host
$Path = "C:\Sandbox\"
$Items = Get-ChildItem $Path -recurse -Include "*.cob", "*.pco"
From your garbled code am guessing you want to return a list of files that have .cob and .pco file extensions. You could use the above code to gather those.
$File = $Items.name
$FullName = $items.fullname
Write-Host $Items.name
$File
$FullName
Adding the above lines will allow you to display them in various ways. You can pick the one that suites your needs then loop through them on a for-each.
As a rule its not a place for code to be writen for you, but you have tried to add some to the questions so I've taken a look. Sometimes you just want a nudge in the right direction.