Long story short, I'm trying to dynamically use a parameter -Directory or -File in PowerShell's Get-ChildItem. Guess what? I'm unable to.
Here's the deal (note: pseudo-code):
Param(
[string]$filter = $(throw "Error: name"),
[string]$type = $(throw "error: file or directory")
)
if( $type -eq "file" ) {
$mode = '-File'
}
elseif( $type -eq "directory" ) {
$mode = '-Directory'
}
Function Find_Plugin_folder {
Write-Host "look for: '$($filter)'"
Invoke-Command -ComputerName (Get-Content servers.txt ) -ScriptBlock {
(Get-ChildItem -Path "z:\www" -Depth 5 $Using:mode -Filter $Using:filter -Recurse ) | %{$_.FullName}
} -ThrottleLimit 80
}
Find_Plugin_folder
$Using:mode is where it throws an error, either:
PS C:\Users\janreilink> v:\test.ps1 vevida-optimizer file
look for: 'vevida-optimizer'
A positional parameter cannot be found that accepts argument '-File'.
+ CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
+ PSComputerName : webserver-01.example.net
Or
PS C:\Users\janreilink> v:\test.ps1 vevida-optimizer directory
look for: 'vevida-optimizer'
A positional parameter cannot be found that accepts argument '-Directory'.
+ CategoryInfo : InvalidArgument: (:) [Get-ChildItem], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand
+ PSComputerName : webserver-01.example.net
I've been reading about Dynamic Parameter sets all afternoon, but can't wrap my head around it yet. Any points are much (much, much) appreciated.
You'll want to use splatting for this instead. Start by creating a hashtable with some or all of the parameters you want to pass:
$dynamicArgs = #{}
if( $type -eq "file" ) {
$dynamicArgs['File'] = $true
}
elseif( $type -eq "directory" ) {
$dynamicArgs['Directory'] = $true
}
Then, inside Invoke-Command, prefix the variable name with # to indicate that you want to "splat" the arguments:
Get-ChildItem -Path "z:\www" -Depth 5 #Using:dynamicArgs -Filter $Using:filter -Recurse
If the splatting table contains the key File with a value of $true, it's the equivalent of adding -File:$true on the command line, and vice versa for the Directory argument
Related
I am having some trouble validating a file input in a script. Using the following function
Function pathtest {
[CmdletBinding()]
param (
[ValidateScript({
if (-Not ($_ | Test-Path -PathType Leaf) ) {
throw "The Path argument must be a file. Folder paths are not allowed."
}
return $true
})]
[System.IO.FileInfo]$Path
)
Write-Host ("Output file: {0}" -f $Path.FullName)
}
Then I call the function with these two input files
pathtest -Path c:\temp\test.txt
pathtest -Path c:\temp\test.csv
The first one (test.txt) returns the path, but the second one (test.csv) returns an error:
PS C:\> pathtest c:\it\test.txt
Output file: c:\it\test.txt
PS C:\> pathtest c:\it\test.csv
pathtest : Cannot validate argument on parameter 'Path'. The Path argument must be a file. Folder paths are not
allowed.
At line:1 char:10
+ pathtest c:\it\test.csv
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [pathtest], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,pathtest
Any idea what's up with this?
Test-Path -Leaf also returns $false when the specified path doesn't exist.
Therefore, you need to test for existence separately, so as to distinguish between an existing path that happens to be a folder and a non-existent path.
Try the following instead:
Function pathtest {
[CmdletBinding()]
param (
[ValidateScript({
$item = Get-Item -ErrorAction Ignore -LiteralPath $_
if (-not $item) {
throw "Path doesn't exist: $_"
}
elseif ($item.PSIsContainer) {
throw "The Path argument must be a file. Folder paths are not allowed."
}
return $true
})]
[System.IO.FileInfo] $Path
)
Write-Host ("Output file: {0}" -f $Path.FullName)
}
Just laying this out there. Here is my code for downloading an application and installing it.
# File Download and Install Function
function FDL($url){
# set to the default download directory; obviously can be wherever one wants
$DL = set-location $env:USERPROFILE\downloads\
# using this to capture just the filename
$FN = $url -split("/")
$FD = $FN[$FN.Length-1]
# Download File
Start-BitsTransfer -source $url -destination $DL\$FD
# Install File
Start-Process -NoNewWindow $DL\$FD -ArgumentList $args
}
PS:> FDL "https://www.kymoto.org/downloads/ISStudio_Latest.exe"
This function works perfectly every time assuming that the URL is correct!
Then I thought, what if I were to have the functionality to place the correct arguments for the installer type. So I came up with this:
# File Download and Install Function
function FDL($url,$p){
# set to the default download directory; obviously can be whereever one wants
$DL = set-location $env:USERPROFILE\downloads\
# using this to capture just the filename
$FN = $url -split("/")
$FD = $FN[$FN.Length-1]
switch ($p){
1 {" /passive /qb /norestart";break}
2 {" /sp- /silent /norestart /SUPPRESSMSGBOXES /CURRENTUSERS /NORESTART /NOCANCEL /FORCECLOSEAPPLICATION /RESTARTAPPLICATIONS";break}
3 {" /SILENT";break}
4 {" /quiet";break}
5 {" /S";break}
6 {" /Q";break}
}
Start-BitsTransfer -source $url -destination $DL\$FD
Start-Process -NoNewWindow $DL\$FD -ArgumentList $p
}
# 2 because this is an InnoSetup installer type
PS:> FDL 'https://www.kymoto.org/downloads/ISStudio_Latest.exe', 2
FAIL
Start-BitsTransfer : The number of items specified in the Source parameter do not match the number of items specified in the Destination parameter. Verify that the same
number of items is specified in the Source and Destination parameters.
At [dir]\FileDownloader Function.ps1:17 char:1
+ Start-BitsTransfer -source $url -destination $DL\$FD
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Start-BitsTransfer], ArgumentException
+ FullyQualifiedErrorId : StartBitsTransferArgumentException,Microsoft.BackgroundIntelligentTransfer.Management.NewBitsTransferCommand
Start-BitsTransfer :
At [dir]\FileDownloader Function.ps1:17 char:1
+ Start-BitsTransfer -source $url -destination $DL\$FD
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Start-BitsTransfer], Exception
+ FullyQualifiedErrorId : System.Exception,Microsoft.BackgroundIntelligentTransfer.Management.NewBitsTransferCommand
Start-Process : Cannot validate argument on parameter 'ArgumentList'. The argument is null or empty. Provide an argument that is not null or empty, and then try the
command again.
At [dir]\FileDownloader Function.ps1:20 char:50
+ Start-Process -NoNewWindow $DL\$FD -ArgumentList ($p)
+ ~~~~
+ CategoryInfo : InvalidData: (:) [Start-Process], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.StartProcessCommand
No matter how I adjust this code it comes out with the same error. Any suggestions or assistance here will be greatly appreciated!
Might not be exactly what you're looking for but it should give you a hint as to approach the code for a function in PowerShell.
A few pointers, parameters in PowerShell are either Positional or Named, about_Parameters explains both concepts. Most importantly, each argument is separated by a space and not by a comma.
You can parse an URL using the Uri Class, so, for getting the file name from your address, is as simple as:
# Last segment from this Uri (index -1 from the segment array)
([uri] 'https://www.kymoto.org/downloads/ISStudio_Latest.exe').Segments[-1]
-ArgumentList from Start-Process expects string[], you can pass an array of arguments instead of a single string as shown in Example 7.
You're never capturing the output from your switch ($p), which explains the error:
Start-Process : Cannot validate argument on parameter 'ArgumentList'. The argument is null or empty.
A hash table can be used instead of a switch.
Lastly, I have added a -PassThru switch, now if you call the function with the switch activated (DownloadFile -PassThru -Uri ...), the function will output the Process instance representing the started process.
function DownloadFile {
[cmdletbinding()]
param(
[parameter(Mandatory)]
[uri] $Uri,
[parameter()]
[ValidateSet(1,2,3,4,5,6)]
[int] $Arguments,
[parameter()]
[string] $Destination = "$env:USERPROFILE\Downloads",
[parameter()]
[switch] $PassThru
)
$arg = #{
1 = '/passive', '/qb', '/norestart'
2 = #(
'/sp-', '/silent', '/norestart', '/SUPPRESSMSGBOXES'
'/CURRENTUSERS', '/NORESTART', '/NOCANCEL'
'/FORCECLOSEAPPLICATION', '/RESTARTAPPLICATIONS'
)
3 = '/SILENT'
4 = '/quiet'
5 = '/S'
6 = '/Q'
}
$destFile = Join-Path $Destination -ChildPath $Uri.Segments[-1]
Start-BitsTransfer -Source $Uri -Destination $destFile
$param = #{
FilePath = $destFile
ArgumentList = $arg[$Arguments]
NoNewWindow = $true
PassThru = $PassThru.IsPresent
}
Start-Process #param
}
DownloadFile -Uri 'https://www.kymoto.org/downloads/ISStudio_Latest.exe' -Arguments 2
if ($mbcb1.Checked -eq $true) {$dgr = "-AutoStart"}
if ($mbcb2.Checked -eq $true) {$dgrc = "-AutoComplete"}
if ($mbcb3.Checked -eq $true) {$dgren = "-NotificationEmails"}
New-MigrationBatch -Name $mbnd -SourceEndpoint $mbcx -TargetDeliveryDomain $mbtdd -CSVData ([System.IO.File]::ReadAllBytes("$cmbcsvfile")) $dgr $dgrc $dgren admin#admin.com
Error :
A positional parameter cannot be found that accepts argument '-Autostart'.
+ CategoryInfo : InvalidArgument: (:) [New-MigrationBatch], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,New-MigrationBatch
+ PSComputerName : ps.outlook.com
if i given direct input its working but passing as variable throwing error.
If you want to optionally specify parameters, use splatting:
$OptionalParameters = #{
AutoStart = $mbcb1.Checked
AutoComplete = $mbcb2.Checked
}
if ($mbcb3.Checked) {
$OptionalParameters["NotificationEmails"] = 'admin#admin.com'
}
New-MigrationBatch -Name $mbnd -SourceEndpoint $mbcx -TargetDeliveryDomain $mbtdd -CSVData ([System.IO.File]::ReadAllBytes("$cmbcsvfile")) #OptionalParameters
We simply build a hashtable with the parameter names and their arguments, and then supply it to the cmdlet as an argument (but like #name instead of $name), and then the parser will turn each entry in the hashtable into a named parameter in the form -key:value.
Finally, the $mbcb3.Checked -eq $true comparison is redundant, since Checked (assuming that $mbcb3 is a checkbox) is already either $true or $false
See the about_Splatting help file for more details about parameter splatting
Here is my for loop
function Generate_bin($input_1, $bin_file_name, $Codes){
$counter=1
foreach ($Code in $Codes)
{
Write-Host "Code = $Code"
$input_11 = "$input_1" + "$counter"
$pattern = "0x"
$New = ""
$Code = [regex]::replace($Code, $pattern, "$New")
$bin_file_name2 = "$bin_file_name"
$bin_file_name2 += "$Code" + ".bin"
#This utility generates "out.bin"
Invoke-Command -ScriptBlock {.\xyz.exe -i -t "$input_1"}
Rename-Item out.bin -NewName $bin_file_name2
$counter++
}
}
I am getting following error
Rename-Item : Cannot create a file when that file already exists.
At C:\script\myParser_unique.ps1:18 char:7
+ Rename-Item out.bin -NewName $bin_file_name2
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\Users\v-ashi...Unified\out.bin:String) [Rename-Item], IOException
+ FullyQualifiedErrorId : RenameItemIOError,Microsoft.PowerShell.Commands.RenameItemCommand
You should add -Force parameter to the Rename-Item to make it overwrite existing files.
If your code happens to generate multiple equal file names you could add a counter to the filename.
Hi Basically i'm having an issue i need to load Arguments in to the following code located at the $URL & $Output but i need it to work with the Join-Path but every time i try it kicks back a load of errors i'm new to this so i am trying everything to get it working I've been asked to do this by my boss completely out my depth but never been one to shy away from a challenge
Function DownloadFileFromURL
{
Add-Type -AssemblyName Microsoft.Visualbasic
#$url = 'http://download.microsoft.com/download/F/4/2/F42AB12D-C935-4E65-9D98-4E56F9ACBC8E/wpilauncher.exe'
$url = $args[0]
$filename = Split-Path -leaf $url
$output = Join-Path $args1 + $filename
$response = [System.Net.WebRequest]::Create($url).GetResponse()
$realurl = $response.ResponseUri.OriginalString
$response.Close()
(New-Object Net.WebClient).DownloadFile($url, $output)
#################Time Taken To Download Files######################
$start_time = Get-Date
Write-Output "Time taken: $((Get-Date).Subtract($start_time).Seconds) second(s)"
}
DownloadFileFromURL ('http://download.microsoft.com/download/F/4/2/F42AB12D-C935-4E65-9D98-4E56F9ACBC8E/wpilauncher.exe','C:\Users\Martin.beardmore\Downloads\test')
Errors being Recieved
Join-Path : Cannot bind argument to parameter 'Path' because it is null.
At C:\Users\Martin.beardmore\Documents\Query download.ps1:6 char:21
+ $output = Join-Path $args[1] + $filename
+ ~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Join-Path], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand
Cannot convert argument "requestUri", with value: "System.Object[]", for "Create" to type "System.Uri": "Cannot convert the "System.Object[]" value of type "System.Object[]" to type
"System.Uri"."
At C:\Users\Martin.beardmore\Documents\Query download.ps1:7 char:1
+ $response = [System.Net.WebRequest]::Create($url).GetResponse()
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
You cannot call a method on a null-valued expression.
At C:\Users\Martin.beardmore\Documents\Query download.ps1:9 char:1
+ $response.Close()
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Cannot convert argument "address", with value: "System.Object[]", for "DownloadFile" to type "System.Uri": "Cannot convert the "System.Object[]" value of type "System.Object[]" to type
"System.Uri"."
At C:\Users\Martin.beardmore\Documents\Query download.ps1:10 char:1
+ (New-Object Net.WebClient).DownloadFile($url, $output)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument
You need to remove the + between the paths you want to join. Look at the syntax for Join-Path.
PS> Get-Command Join-Path -Syntax
Join-Path [-Path] <string[]> [-ChildPath] <string> [-Resolve] [-Credential <pscredential>] [-UseTransaction] [<CommonParameters>]
You should use it like $output = Join-Path $args1 $filename
Also, where does $args1 come from? In functions, you should name your parameters. The order in which you define them will be the order they are binded to, so they will still behave the same as arguments, but they are easier to work with.
Function DownloadFileFromURL ($url, $folder)
{
Write-Host "URL: $url"
Write-Host "Folder: $folder"
}
Sample:
PS > DownloadFileFromURL stackoverflow.com c:\folder
URL: stackoverflow.com
Folder: c:\folder
You are also calling the function wrong. Comma is used to seperate objects in an array, so DownloadFileFromURL ('http://download.microsoft.com/download/F/4/2/F42AB12D-C935-4E65-9D98-4E56F9ACBC8E/wpilauncher.exe','C:\Users\Martin.beardmore\Downloads\test') results in:
$url = $args[0] = #('http://download.microsoft.com/download/F/4/2/F42AB12D-C935-4E65-9D98-4E56F9ACBC8E/wpilauncher.exe','C:\Users\Martin.beardmore\Downloads\test')
Modify your function to use parameters and call it like I've as shown above and it should work.
Updated script:
Function DownloadFileFromURL ($url, $destination)
{
Add-Type -AssemblyName Microsoft.Visualbasic
$filename = Split-Path -Leaf $url
$output = Join-Path $destination $filename
$response = [System.Net.WebRequest]::Create($url).GetResponse()
$realurl = $response.ResponseUri.OriginalString
$response.Close()
(New-Object Net.WebClient).DownloadFile($url, $output)
#################Time Taken To Download Files######################
$start_time = Get-Date
Write-Output "Time taken: $((Get-Date).Subtract($start_time).Seconds) second(s)"
}
DownloadFileFromURL 'http://download.microsoft.com/download/F/4/2/F42AB12D-C935-4E65-9D98-4E56F9ACBC8E/wpilauncher.exe' 'C:\Users\Martin.beardmore\Downloads\test'
#or even better
#DownloadFileFromURL -url 'http://download.microsoft.com/download/F/4/2/F42AB12D-C935-4E65-9D98-4E56F9ACBC8E/wpilauncher.exe' -destination 'C:\Users\Martin.beardmore\Downloads\test'