empty string error when binding parameters - indirect splatting - powershell

I trying to run the this job with paramaters
$courses = {
param($securitytoken_path_a1 ,$EmailPasswordPath_a1 ,$EmailTo_a1)
Write-Host $securitytoken_path_a1 | Format-Table -Property *
C:\Users\so\Desktop\CanvasColleagueIntergration\PowerShells\DownloadInformation.ps1 -securitytoken_path ($securitytoken_path_a1) -emailPasswordPath $EmailPasswordPath_a1 -object "courses" -EmailTo $EmailTo_a1 -test $false
}
I am passing these parameters
$args1 = #{ "securitytoken_path_a1" = "C:\Credentials\CANVAS_API_PROD_FRANCO.TXT" ; "EmailPasswordPath_a1" = "C:\Credentials\EMAILFRANCO.txt"; "EmailTo_a1" = 'fpettigrosso#holyfamily.edu'}
when I invoke the job with this command it fails
Start-Job -ScriptBlock $courses -Name "Test" -ArgumentList $args1
when I try to see what is the issue I get the error back
Cannot bind argument to parameter 'emailPasswordPath' because it is an empty string.
+ CategoryInfo : InvalidData: (:) [DownloadInformation.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,DownloadInformation.ps1
+ PSComputerName : localhost
help

What you're looking for is splatting: the ability to pass a set of parameter values via a hashtable (or, less commonl, via an array) to a command.
Generally, in order to signal the intent to splat, a special sigil - # is required, so as to distinguish it from a single argument that just happens to be a hashtable:
$args1 passes a single argument that happens to be a hashtable.
#args1 - note how sigil $ has been replaced with # - tells PowerShell to apply splatting, i.e., to consider the hashtable's key-value pairs to be parameter-name-value pairs (note that the hashtable keys mustn't start with -, which is implied)
However, splatting only works directly for a given command, and you cannot relay a splatted hashtable via a command's single parameter.
That is, attempting to use -ArgumentList #args1 actually fails.
Your own solution works around that by passing the hashtable as-is to the script block and then explicitly accessing that hashtable's entries one by one.
An alternative solution is to use the hashtable argument to apply splatting inside the script block:
$courses = {
param([hashtable] $htArgs) # pass the hashtable - to be splatted later - as-is
$script = 'C:\Users\fpettigrosso\Desktop\CanvasColleagueIntergration\PowerShells\DownloadInformation.ps1'
& $script #htArgs # use $htArgs for splatting
}
Note, however, that the target command's parameter names must match the hashtable keys exactly (or as an unambiguous prefix, but that's ill-advised), so the _a1 suffix would have to be removed from the keys.
If modifying the input hashtable's keys is not an option, you can use the following command to create a modified copy whose keys have the _a1 suffix removed:
# Create a copy of $args1 in $htArgs with keys without the "_a1" suffix.
$args1.Keys | % { $htArgs = #{} } { $htArgs.($_ -replace '_a1$') = $args1.$_ }

I changed the parameters in the $courses so it will take a hashtable
$courses = {
param($a1)
Write-Host $a1.securitytoken_path_a1 | Format-Table -Property *
C:\Users\fpettigrosso\Desktop\CanvasColleagueIntergration\PowerShells\DownloadInformation.ps1 -securitytoken_path $a1.securitytoken_path_a1 -emailPasswordPath $a1.EmailPasswordPath_a1 -object "courses" -EmailTo $a1.EmailTo_a1 -test $false
}

Related

powershell : pipe get-content to ps1 file with parameters

I'm trying to write a script which uses the powershell cmdlet get-content tail and inserts the new lines into the sql server table. i can't get the syntax to pipe the tail to the sqlinsert.ps1 file that handles the table insert.
i'm looking for help on how to pipe "get-content tail" to a sqlinsert.ps1 file to do a sql database insert statement using the following :
$startTime = get-date
Write-Host "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt"
get-content "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt" -tail 1 -wait | & "sqlinsert.ps1" -stmp $("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error" -msg $_
# % { "$_ read at $(Get-Date -Format "hh:mm:ss")" }
in the sqlinsert.ps1 :
param ([string]$stmp, [string]$method, [string]$msg )
$Connection = New-Object System.Data.SQLClient.SQLConnection
$Connection.ConnectionString = "server='$serverName';database='$databaseName';User ID = $uid; Password = $pwd;"
$Command = New-Object System.Data.SQLClient.SQLCommand
$Command.Connection = $Connection
$sql = "insert into [tbl_iiserrors] (errstamp, method, msg) values (#stmp , #method, #msg) "
.
.
.
error i get:
& : The term 'sqlinsert.ps1' is not recognized as the name of a
cmdlet, function, script file, or operable program. Check the spelling
of the name, or if a path was included, verify that the path is
correct and try again. At C:\Temp\ob\iislog\tst_tail.ps1:3 char:95
... Mdd}" -f (get-date)).txt" -tail 1 -wait | & "sqlinsert.ps1" -stmp $ ...
~~~~~~~~~~~~~~~
CategoryInfo : ObjectNotFound: (sqlinsert.ps1:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
Suggestion [3,General]: The command sqlinsert.ps1 was not found, but
does exist in the current location. Windows PowerShell does not load
commands from the current location by default. If you trust this
command, instead type: ".\sqlinsert.ps1". See "get-help
about_Command_Precedence" for more details.
The sqlinsert.ps1 works when i run it from powershell command :
PS c:\temp> .\sqlinsert -stmp 2020-11-20 00:00:00 -method 'eek' -msg 'uh hello'
In order to bind pipeline input to a parameter, you need to decorate it with a [Parameter] attribute and specify that it accepts pipeline input, like this:
param (
[string]$stmp,
[string]$method,
[Parameter(ValueFromPipeline = $true)]
[string]$msg
)
See the about_Functions_Advanced_Parameters help file for more details about how to modify the behavior of parameters
By design, for security reasons, PowerShell requires you to signal the intent to execute a script located in the current directory explicitly, using a path - .\sqlinsert.ps1 - rather than a mere file name - sqlinsert.ps1; that is what the suggestion following the error message is trying to tell you.
Note that you only need &, the call operator, if the script path is quoted and/or contains variable references - and .\sqlinsert.ps1 doesn't require quoting.
You can only use the automatic $_ variable, which represents the current input object from the pipeline inside a script block ({ ... }), such as one passed to the ForEach-Object cmdlet, which invokes that block for each object received via the pipeline.
Re the content of your script: Inside expandable strings ("..."), you cannot use # to refer to variables to be expanded (interpolated); use regular, $-prefixed variable references or $(...), the subexpression operator to embed expressions; also, it looks like you're inserting string values into the SQL table, so you'll have to enclose the expanded variable values in embedded '...'
$startTime = get-date
Get-Content "\\iisserver\logs\Logs-$("{0:yyyyMMdd}" -f (get-date)).txt" -Tail 1 -Wait |
ForEach-Object {
.\sqlinsert.ps1 -stmp ("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error" -msg $_
}
The alternative to using a ForEach-Object call is to modify your script to directly receive its -msg argument from the pipeline, as shown in Mathias' answer, in which case you must omit the -msg $_ argument from your script call:
Get-Content ... |
.\sqlinsert.ps1 -stmp ("{0:yyyy-MM-dd hh:mm:ss.fff}" -f (get-date)) -method "Error"

A positional parameter cannot be found that accepts argument 't'

I am getting the following error
New-AzResourceGroup : A positional parameter cannot be found that accepts argument 't'.
At line:1 char:1
+ New-AzResourceGroup -Name #rgName -Location #location -Tag #{LoB="pla ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-AzResourceGroup], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.NewAzureResourceGrou
pCmdlet
while trying to create a new resource group with the following code. Where is the issue?
$rgName = "storage-dev-rg"
$location = "eastus"
New-AzResourceGroup -Name #rgName -Location #location -Tag #{LoB="platform"; CostCenter="IT"}
To quote your own answer:
The declared variables should be referenced using $, not with #.
about_Variables explains that in order to create and later reference variables in PowerShell, you prefix their name with sigil $ in both cases; i.e., $rgName and $location in your case.
You only ever prefix a variable name with sigil # if you want to perform splatting (see about_Splatting).
(The sigil # has other uses too, namely as #(...), the array-subexpression operator, and as #{ ... }, a hashtable literal, as also used in your command.)
Splatting is used to pass an array-like value stored in a variable as individual positional arguments or, more typically, to bind the entries of a hashtable containing parameter name-value pairs to the parameters so named - see this answer.
Since your variables contain strings and strings can be treated as an array-like collection of characters (via the System.Collections.IEnumerable interface), splatting a string variable effectively passes each character as a separate, positional argument.
PS> $foo = 'bar'; Write-Output #foo # same as: Write-Output 'b' 'a' 'r'
b
a
r
As for what you tried:
-Name #rgName, based on $rgName containing string 'storage-dev-rg', passed 's' - the 1st char only - to -Name, and the remaining characters as individual, positional arguments. 't', the 2nd character, was the first such positional argument, and since New-AzResourceGroup didn't expect any positional arguments, it complained about it.
I figured it out. The declared variables should be referenced using $, not with #.

Pointer or reference variables

I want to use indirect reference variable.
I am setting this at Command Prompt
SET RiskScheduler=true
My code is like this
Write-Host "$Env:RiskScheduler" # prints true
I want to achieve the same should be printed with code like this
$name='RiskScheduler'
Write-host $name # prints RiskScheduler
Write-Host $Env:$name # gives error
The error I am getting is
Cannot process argument because the value of argument "path" is invalid.
Change the value of the "path" argument and run the operation again.
At D:\tmp\buildtools\udclient.6.2\ud_clean.PS1:37 char:17
+ Write-Host $Env: <<<< `$name`
+ CategoryInfo : InvalidArgument: (:) [], PSArgumentException
+ FullyQualifiedErrorId : Argument
I am looking for something that shall first evaluate $name and then evaluate $Env:(value of $name).
Can someone please suggest, what the correct syntax is?
Option 1
You can do this with:
$x = 'RiskScheduler'
Write-Host (Get-Item env:$x).Value
Which will output true in your case
If you have a list of variable names:
$varNames = #('COMPUTERNAME', 'SESSIONNAME', 'RiskScheduler')
ForEach($varName in $varNames) {
Write-Host (Get-Item env:$varName).Value
}
Which will output:
MyPcName
Console
true
You can find more information about this by entering Get-Help about_environment_variables into PowerShell:
Get-Item -Path Env:* | Get-Member
Displaying Environment Variables
You can use the cmdlets that contain the Item noun (the Item cmdlets) to
display and change the values of environment variables. Because
environment variables do not have child items, the output of Get-Item
and Get-ChildItem is the same.
When you refer to an environment variable, type the Env: drive name
followed by the name of the variable. For example, to display the value
of the COMPUTERNAME environment variable, type:
Get-Childitem Env:Computername
Option 2
Another option would be to use this function:
function Get-EnvVar($Name) {
$allVars = dir env:
foreach ($var in $allVars) {
If ($var.Name -eq $Name) {
return $var
}
}
}
The function iterates around all available environment variables and returns the one you are after (in this case, $Env:COMPUTERNAME).
You can then call
Write-Host $myvar.Value
to display the value of COMPUTERNAME
If you have an array of variable names you want the value of:
$varNames = #('COMPUTERNAME', 'SESSIONNAME', 'RiskScheduler')
ForEach($varName in $varNames) {
$var = Get-EnvVar -Name $varName
Write-Host $var.Value
}
Which outputs (for me) :
MyPcName
Console
true

What is conceptually wrong with get-date|Write-Host($_)

I'm trying to understand Powershell, but find somethings not so intuitive. What I understand of it is that in the pipeline objects are passed, instead of traditionally text. And $_ refers to the current object in the pipeline. Then, why is the following not working:
get-date|Write-Host "$_"
The errormessage is:
Write-Host : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not matc
h any of the parameters that take pipeline input.
At line:1 char:10
+ get-date|Write-Host $_
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (10-9-2014 15:17:00:PSObject) [Write-Host], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.WriteHostCommand
$_ is the current single item in the pipeline. To write each item in the pipeline you would write
get-data | foreach { Write-Host $_ }
Or in the short form
get-data |% { Write-Host $_ }
Conceptually, Foreach is a cmdlet that receives a function parameter, a pipeline input and applies the function on each item of the pipeline. You can't just write code with $_ - you need to have a function explicitly states that it agrees to receive pipeline input
And $_ refers to the current object in the pipeline
Indeed, the automatic $_ variable refers to the current pipeline object, but only in script blocks { ... }, notably those passed to the ForEach-Object and Where-Object cmdlets.
Outside of script blocks it has no meaningful value.
Therefore, the immediate fix to your command is the following:
Get-Date | ForEach-Object { Write-Host $_ }
However, note that:
Write-Host is is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file.
To output a value, use it by itself; e.g, $value, instead of Write-Host $value (or use Write-Output $value); see this answer. To explicitly print only to the display but with rich formatting, use Out-Host.
Therefore, if merely outputting each pipeline input object is the goal, Get-Date | ForEach-Object { $_ } would do, where the ForEach-Object call is redundant if each input object is to simply be passed through (without transformation); that is, in the latter case just Get-Date would do.
As for what you tried:
get-date|Write-Host "$_"
As noted, the use of $_ in this context is pointless, but the reason for the error message you saw is unrelated to that problem:
Instead, the reason for the error is that you're mistakenly trying to provide input to Write-Host both via the pipeline Get-Date | Write-Host ... and by way of an argument (... | Write-Host "...")
Given that the argument ("$_") (positionally) binds to the -Object parameter, the pipeline input then has no parameter left to bind to, which causes the error at hand.

Powershell: Args/params not being populated

I have a PowerShell script:
param (
[Parameter(Mandatory=$true)][string]$input,
[Parameter(Mandatory=$true)][string]$table
)
Write-Host "Args:" $Args.Length
Get-Content $input |
% { [Regex]::Replace($_, ",(?!NULL)([^,]*[^\d,]+[^,]*)", ",'`$1'") } |
% { [Regex]::Replace($_, ".+", "INSERT INTO $table VALUES (`$1)") }
The Write-Host part is for debugging.
I run it as .\csvtosql.ps1 mycsv.csv dbo.MyTable (from powershell shell), and get
Args: 0
Get-Content : Cannot bind argument to parameter 'Path' because it is an empty s
tring.
At C:\temp\csvtosql.ps1:7 char:12
+ Get-Content <<<< $input |
+ CategoryInfo : InvalidData: (:) [Get-Content], ParameterBinding
ValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl
lowed,Microsoft.PowerShell.Commands.GetContentCommand
I get exactly the same error with any parameters that I pass, also the same error if I try to use named parameters.
What can cause parameters not to be passed in?
UPDATE: PowerShell ISE asks me for these parameters using GUI prompts, then gives me the same error about them not being passed in.
Unless you marked a parameter with the ValueFromRemainingArguments attribute (indicates whether the cmdlet parameter accepts all the remaining command-line arguments that are associated with this parameter), Args is "disabled". If all you need is the arguments count call the special variable:
$PSBoundParameters.Count
Do not mix. Make use of $args or parameters.
Also do note that $input is a special variable, don't declare it as a parameter. http://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/
You're calling your script with positional parameters (i.e. unnamed) and PowerShell doesn't know how to map them to your script parameters. You need to either call your script using the parameter names:
.\csvtosql.ps1 -input mycsv.csv -table dbo.MyTable
or update your script to specify your preferred order of positional parameters:
param (
[Parameter(Mandatory=$true,Position=0)]
[string]
$input,
[Parameter(Mandatory=$true,Position=1)]
[string]
$table
)