How to pass the full path of a file as an argument to a new instance of Powershell? - powershell

This is the code of my script that calls another script:
$LocalFolder = "$env:USERPROFILE\Desktop\Banana Productions"
$RenderClient = "$LocalFolder\Render\Modelo 02\Cliente 02"
$CutFolder = "$RenderClient\Cortar"
$FFMpegScript = "$CutFolder\ffmpeg crop.ps1"
gci "$RenderClient\Cortar" *.mp4 -File -Recurse | ForEach-Object {
$FilePath = $_.FullName
start PowerShell "-NoExit", "-File", "`"$FFMpegScript`"", "$FilePath"
Write-Host $FilePath
}
The issue is that I am not able to pass the argument with the value of $_.FullName to the new instance, I get an error message in the new instance with the message:
Cannot process argument because the value of argument "name" is not valid
This is all that's in the script I'm calling:
param($args)
Write-Host $args[0]
Read-Host -Prompt "Press Enter to continue"
How can I resolve this?

Due to a long-standing bug in Start-Process - see GitHub issue #5576 - it is best to pass a single string argument to the (positionally implied) -ArgumentList parameter, which allows you to control the process command line explicitly:
The following uses an expandable here-string for syntactic convenience:
Get-ChildItem "$RenderClient\Cortar" -Filter *.mp4 -File -Recurse |
ForEach-Object {
$FilePath = $_.FullName
Start-Process PowerShell #"
-NoExit -File "$FFMpegScript" "$FilePath"
"# # Note: This must be at the very start of the line.
}
Additionally, do not use the automatic $args variable as a custom variable.
In fact, if you want your script to receive positional arguments only, there is no need for a formal param(...) declaration - just use the array-valued $args variable as-is:
# Without a param(...) declaration, $args *implicitly* contains
# any arguments passed, as an array.
$args[0] # output the 1st argument passed.
Read-Host -Prompt "Press Enter to continue"

Related

Copy files from my current directory to new directory

so I am trying to copy 2 files from same folder that my Powershell script is in. I have created script there and also 2 files Moveit1.txt, Moveit2.txt my script is this :
$ScriptDirectory = get-childitem -path $PSScriptRoot
Copy-Item $ScriptDirectory\MoveIt1.txt $env:USERPROFILE + "\AppData\Roaming\" -Force
Copy-Item $ScriptDirectory\MoveIt2.txt "C:\Program Files (x86)\" -Force
But unfortunately it says the files can't be found? But if I check just line $ScriptDirectory it shows where its located and with the files inside. What am I doing wrong?
There is one thing to note:
$ScriptDirectory = Get-ChildItem -Path $PSScriptRoot
$scriptDirectory will most likely contain the 2 MoveIt files in addition to your .ps1 script. When you do:
$ScriptDirectory\MoveIt1.txt
I'm guessing it will end up being something like this when interpreted by Copy-Item:
path\to\script\script.ps1\path\to\script\moveit1.txt\path\to\script\moiveit2.txt\moveit1.txt
Try doing this instead:
Copy-Item (Join-Path $PSScriptRoot MoveIt1.txt) (Join-Path $env:USERPROFILE "\AppData\Roaming\") -Force
Copy-Item (Join-Path $PSScriptRoot MoveIt2.txt) "C:\Program Files (x86)\" -Force
Regarding your Access Denied issue and it working when ran as Administrator with hard-coded paths. You can put this at the top of the script so it elevates itself however this will pop an UAC prompt:
$invocation = "-File `"$PSCommandPath`""
Start-Process powershell -Verb Runas -ArgumentList $invocation
The primary problem with your code is a syntax problem (Santiago's helpful answer addresses additional problems):
In order to pass an expression such as
$env:USERPROFILE + "\AppData\Roaming\"
as a command argument, you need to enclose it in (...).
Neglecting to do so passes three arguments, as the following simplified example demonstrates:
# !! WRONG: Passes *three* arguments instead of the result
# of the intended expression.
PS> Write-Output $env:USERPROFILE + "\AppData\Roaming"
C:\Users\jdoe
+
\App\Data\Roaming
# OK: (...) forces a new parsing context, in which the expression is recognized
# as such.
PS> Write-Output $env:USERPROFILE + "\AppData\Roaming"
C:\Users\jdoe\App\Data\Roaming
As an aside:
You could use $env:APPDATA to directly get the path of interest, and
Even in cases where you do need to build a path from multiple strings, it may be simpler to use an expandable string instead of the + operator: "$env:USERPROFILE\AppData\Roaming" - in this particular case, because the string contains neither spaces nor other special characters (other than the intended $), the double quotes are even optional.
See this answer for more information.
try {
$scriptPath = $PSScriptRoot
if (!$scriptPath)
{
if ($psISE)
{
$scriptPath = Split-Path -Parent -Path $psISE.CurrentFile.FullPath
} else {
Write-Host -ForegroundColor Red "Cannot resolve script file's path"
exit 1
}
}
} catch {
Write-Host -ForegroundColor Red "Caught Exception: $($Error[0].Exception.Message)"
exit 2
}
Write-Host "Path: $scriptPath"
Copy-Item $ScriptPath\onexcuiconcif.xml -Destination "$env:APPDATA\Avaya\Avaya one-x Communicator" -Force
Copy-Item $scriptPath\InstallConfig.xml -Destination "C:\Program Files (x86)\Avaya\Avaya one-X Communicator" -Force

Powershell variable contains the value twice

I have a powershell script that is started bij a jenskinsfile. This all works well. But in the script I have a function to download a file. This does not work, because the $filePath variable contains the value twice. When this part would be run the log would look like:
https://example.com/api/download
D:\folder\file_2.txt D:\folder\file_2.txt
How can I get the value only to be there ones in $filePath ?
function DownloadFile ($folder, $version) {
$wc = New-Object System.Net.WebClient
$wc.Headers['Content-Type'] = 'application/json'
$requesturl = "https://example.com/api/download"
$filePath = Join-Path -Path $folder -ChildPath "\file_$version.txt"
Write-Host "$requesturl"
Write-Host "$filePath"
$wc.DownloadFile($requesturl, $filePath)
return $filePath
}
The implication is that the argument you're passing to the -folder parameter (as represented inside your function as the $folder parameter variable) is an array of folder paths, not a single one.
The solution is therefore to make sure that you only pass a single folder path when you call your DownloadFile function; e.g.:
DownloadFile -folder D:\folder -version 2
# With *positional* parameter binding:
DownloadFile D:\folder 2
Since Join-Path accepts an array of paths as a -Path argument, it outputs multiple paths when given an array; e.g.:
PS> $folder = 'c:\abc', 'c:\def'; Join-Path -Path $folder -ChildPath file.txt
c:\abc\file.txt
c:\def\file.txt
Passing an array Write-Host implicitly stringifies it, which means creating a single string composed of the array elements joined with spaces:
PS> $folder = 'c:\abc', 'c:\def'; Write-Host $folder
c:\abc c:\def
(Note that this differs from implicit output / output via Write-Output, which prints each array element on its own line; also, implicit output / Write-Output write to the pipeline, meaning they output data for later processing, whereas Write-Host writes strings to the display).

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.

Hidden variable Password parameter - powershell 4.0

I am creating a release definition with a powershell script to replace a file with env variables from the release definition it works but It doesn't seem to catch the password variable which is hidden in the release definition. is there a way to tell powershell to look for hidden variables?
UPDATE: Here is the script it finds all the variables in $paramsFilePath that are not hidden my password in In environmental variables in Release definition is hidden and the script doesn't find it.
param(
[string]$paramsFilePath,
)
Write-Verbose -Verbose "Entering script Replace-SetParameters.ps1"
Write-Verbose -Verbose ("Path to SetParametersFile: {0}" -f $paramsFilePath)
# get the environment variables
$vars = Get-ChildItem -path env:*
# read in the setParameters file
$contents = Get-Content -Path $paramsFilePath
# perform a regex replacement
$newContents = "";
$contents | % {
$line = $_
if ($_ -match "__(\w+)__") {
$setting = Get-ChildItem -path env:* | ? { $_.Name -eq $Matches[1] }
if ($setting) {
Write-Verbose -Verbose ("Replacing key {0} with value from environment" -f $setting.Name)
$line = $_ -replace "__(\w+)__", $setting.Value
}
}
$newContents += $line + [Environment]::NewLine
}
Write-Verbose -Verbose "Overwriting SetParameters file with new values"
Set-Content $paramsFilePath -Value $newContents
Write-Verbose -Verbose "Exiting script Replace-SetParameters.ps1"
Unlike the normal variable, the password you are trying to get is secret variable.
Secret Variables
We recommend that you make the variable Secret if it contains a
password, keys, or some other kind of data that you need to avoid
exposing.
The variable replacement we do is on the inputs on the tasks, we don't parse the scripts. To use secret variables you will have to take those as inputs into your script we explicitly do not populate those into the environment. You could take a look at this discuss: Use hidden / secret variables in commands

Please explain, function to add string to first line of a file

Would you like to explain what is happing in the PowerShell code at the bottom of this post?
I got my first, lets say "hello world" in PowerShell, and it needed these lines of code. It works like a charm, but I am not sure what it does, exactly.
The questions starts at
$( ,$_; Get-Content $Path -ea SilentlyContinue) | Out-File $Path
So this is what I understand so far.
We create a function called Insert-Content. With the params (input that will be interpeted as a string and will be added to $path).
function Insert-Content {
param ( [String]$Path )
This is what the function does/processes:
process {
$( ,$_;
I am not sure what this does, but I guess it gets "the input" (the "Hello World" before the | in "Hello World" | Insert-Content test.txt).
And then we got -ea SilentylyContinue, but what does it do?
process {
$( ,$_; Get-Content $Path -ea SilentlyContinue) | Out-File $Path
It would be greatly appreciated if you could explain these two parts
$( ,$_;
-ea SilentylyContinue
Code needed/used: Add a string to the first line of a doc.
function Insert-Content {
param ( [String]$Path )
process {
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
}
}
"Hello World" | Insert-Content test.txt
process {...} is used for applying the code inside the scriptblock (the {...}) to each parameter argument that the function reads from a pipeline.
$_ is an automatic variable containing the current object. The comma operator , preceding the $_ converts the string value to a string array with a single element. It's not required, though. The code would work just as well with just $_ instead of ,$_.
Get-Content $Path reads the content of the file $Path and echoes it as to the success output stream as an array of strings (each line as a separate string).
The ; separates the two statements ,$_ and Get-Content $Path from each other.
| Out-File $Path writes the output back to the file $Path.
The subexpression operator $() is required to decouple reading the file from writing to it. You can't write to a file when a process is already reading from it, so the subexpression ensures that reading is completed before writing starts.
Basically this whole construct
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
echoes the input from the pipeline (i.e. the "Hello World") followed by the current content of the file $Path (effectively prepending the input string to the file content) and writes everything back to the file.
The -ea SilentlyContinue (or -ErrorAction SilentlyContinue) suppresses the error that would be thrown when $Path doesn't already exist.
The relevant section of code must be handled as a whole:
$( ,$_;Get-Content $Path -ea SilentlyContinue) | Out-File $Path
First, as others have said, -ea is the shortened version of -ErrorAction. -ErrorAction SilentlyContinue tells the cmdlet "Suppress any error messages and continue executing." See Get-Help about_Common_Parameters -ShowWindow.
Next, the $() is the sub-expression operator. It means "Evaluate what is between the parentheses as its own command and return the result(s)." See Get-Help about_Operators -ShowWindow.
This subexpression here is:
,$_;Get-Content $Path -ea SilentlyContinue
It contains two statements: ,$_ and Get-Content $Path -ea SilentlyContinue. The semicolon is just the end of statement identifier to separate the two.
,$_; is two kind of complex parts.
$_ is the special pipeline variable. It always contains whatever object is in the current pipeline. See Get-Help about_Automatic_Variables -ShowWindow for more about $_ (it's mostly used with ForEach-Object and Where-Object cmdlets, so check those out, too), and Get-Help about_pipelines -ShowWindow for more help with pipelines.
The comma here is the comma operator (see Get-Help about_Operators -ShowWindow again). It creates an array from the objects on either side. For example, 1,2,3 creates an array with three elements 1, 2, and 3. If you want a two item array, you can say 1,2.
What if you want a one item array? Well, you can't say 1, because Powershell will think you forgot something. Instead, you can say ,1.
You can test it with the -is operator:
PS C:\> 1,2,3 -is [Array]
True
PS C:\> 1 -is [Array]
False
PS C:\> ,1 -is [Array]
True
Why might you want to create a one item array? Well, if later on your code is assuming the item is an array, it can be useful. In early editions of Powershell, properties like .Count would be missing for single items.
For completeness, yes, I believe you could write:
$( #($_);Get-Content $Path -ea SilentlyContinue)
And I think you could rewrite this function:
function Insert-Content {
param ( [String]$Path )
process {
#Read from pipeline
$strings = #($_);
#Add content of specified file to the same array
$strings += Get-Content $Path -ea SilentlyContinue;
#Write the whole array to the file specified at $Path
$strings | Out-File $Path;
}
}
So this adds content from the pipeline to the start of a file specified by -Path.
It's also somewhat poor practice not to create a parameter for the pipeline object itself and define it. See... well, see all the topics under Get-Help "about_Functions*", but mostly the Advanced ones. This is an advanced topic.