Pointer or reference variables - powershell

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

Related

Weird PowerShell problem: [ref] cannot be applied to a variable that does not exist

My Powershell script exited with "[ref] cannot be applied to a variable that does not exist" after running a while (it actually worked for a while)
The code snippet is something like
function outputData(...) {
$data = $null
if ($outputQueue.TryTake([ref] $data, 1000) -eq $false) {
continue
}
Write-Host $data
}
The detail errors thrown at the end are as below:
[ref] cannot be applied to a variable that does not exist.
At C:\Program Files\mfile.ps1:1213 char:13
+ if ($outputQueue.TryTake([ref] $data, 1000) -eq $ ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (data:VariablePath) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : NonExistingVariableReference
May i ask if any thoughts about the cause ?
Thanks !
While error messages aren't always helpful, this one is:
It tells you that the $data variable you're trying to use with [ref] must already exist, i.e., must have been created explicitly, which in PowerShell means:
creating it by assigning a value to it - even if that value is $null,
or using New-Variable to create it.
A simplified example:
$data = $null # create variable $data
# OK to use $data with [ref], now that it exists.
# $data receives [int] value 10 in the process.
[int]::TryParse('10', [ref] $data)

ValidateScript unexpectedly returning false when condition is true

I have a PowerShell function I'm writing to build and execute a variety of logman.exe commands for me so I don't have to reference the provider GUIDs and type up the command each time I want to capture from a different source. One of the parameters is the file name and I am performing some validation on the parameter. Originally I used -match '.+?\.etl$' to check that the file name had the .etl extension and additionally did some validation on the path. I later decided to remove the path validation but neglected to change the validation attribute to ValidatePattern.
What I discovered was that while it worked perfectly on the machine I was using to author and validate it, on my Server 2016 Core machine it seemed to misbehave when calling the function but that if I just ran the same check at the prompt it worked as expected.
The PowerShell:
[Parameter(ParameterSetName="Server", Mandatory=$true)]
[Parameter(ParameterSetName="Client", Mandatory=$true)]
[ValidateScript({$FileName -match '.+?\.etl$'}]
[string] $FileName = $null
The Output:
PS C:\Users\Administrator> Start-TBLogging -ServerLogName HTTPSYS -FileName ".\TestLog.etl"
PS C:\Users\Administrator> Start-TBLogging : Cannot validate argument on parameter 'FileName'. The "$FileName -match '.+?\.etl$'" validation script
for the argument with value ".\TestLog.etl" did not return a result of True. Determine why the validation script failed,
and then try the command again.
At line:1 char:50
+ Start-TBLogging -ServerLogName HTTPSYS -FileName ".\TestLog.etl"
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Start-TBLogging], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Start-TBLogging
Trying it manually worked:
PS C:\Users\Administrator> $FileName = ".\TestLog.etl"
PS C:\Users\Administrator> $FileName -match '.+?\.etl$'
True
After changing the function to use ValidatePattern it works just fine everywhere but I was wondering if anyone could shed light on the discontinuity.
As Joshua Shearer points out in a comment on a question, you must use automatic variable $_ (or its alias form, $PSItem), not the parameter variable to refer to the argument to validate inside [ValidateScript({ ... })].
Therefore, instead of:
# !! WRONG: The argument at hand has NOT yet been assigned to parameter
# variable $FileName; by design, that assignment
# doesn't happen until AFTER (successful) validation.
[ValidateScript({ $FileName -match '.+?\.etl$' }]
[string] $FileName
use:
# OK: $_ (or $PSItem) represents the argument to validate inside { ... }
[ValidateScript({ $_ -match '.+?\.etl$' })]
[string] $FileName
As briantist points out in another comment on the question, inside the script block $FileName will have the value, if any, from the caller's scope (or its ancestral scopes).

empty string error when binding parameters - indirect splatting

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
}

How to create one input parameter that can receive empty or non-empty values

I am trying to create function that will receive an argument as log file path.
I want this parameter to be able to receive string path or used as a [switch], without any arguments.
The reason I want to do it is because I have three scenarios I need to cover and I wanted to do it with only one parameter:
1. The parameter is not passed
2. The parameter is passed with empty argument
3. The parameter is passed with argument
Here is a script that demonstrate what I want:
function myFunc(){
param(
$LogFile = $null
)
# 1. PS > myFunc
if($LogFile -eq $null){
}
# 2. PS > myFunc -LogFile
if($LogFile -eq ""){
}
# 3. PS > myFunc -LogFile "C:\tmp\log.txt"
else{
}
}
Is it possible to create such input parameter that can receive empty and non-empty values ?
When I run myFunc -LogFile I receive an error:
myFunc : Missing an argument for parameter 'LogFile'. Specify a parameter of type 'System.Object' and try again.
At line:1 char:8
+ myFunc -LogFile
+ ~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [myFunc], ParameterBindingException
+ FullyQualifiedErrorId : MissingArgument,myFunc
This is because it is not set to [switch] and if I add the [switch] than I can't run myFunc -LogFile c:\tmp\file.txt:
myFunc : A positional parameter cannot be found that accepts argument 'C:\tmp\file.txt'.
At line:1 char:1
+ myFunc -LogFile C:\tmp\file.txt
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [myFunc], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,myFunc
I also tried to use [AllowNull()] and [AllowEmptyString()] but they still require to some char.
More information about the parameters can find here.
I don't think you can have a parameter acting like a switch as well as like string input. This is because if you try to use it like a switch it will complain that you didn't pass it any input:
myFunc : Missing an argument for parameter 'LogFile'. Specify a
parameter of type 'System.Object' and try again.
One way to get the behavior you're looking for would be to have $LogFile as a switch and then have a second parameter that could take the string (which you wouldn't need to explicitly declare as the input would go to this parameter by the order its called in -- giving the impression it's being passed to -LogFile). Here's an example of that:
function myFunc{
param(
[switch]$LogFile,
[string]$LogPath = $null
)
# 1. PS > myFunc
if ($LogFile -eq $false){
"Function used without parameter"
}
# 2. PS > myFunc -LogFile
elseif ($LogFile -eq $true -and -not $LogPath){
"Parameter used like a switch"
}
# 3. PS > myFunc -LogFile "C:\tmp\log.txt"
else{
"Parameter was $LogPath"
}
}
myfunc
myfunc -logfile
myfunc -logfile "C:\tmp\log.txt"
To be honest though, scernario 2 is sort of not necessary on the assumption that all you want to do in this case is have a default path. In which case, you could just give $LogPath a default in the param() block. It's also probably not a good idea to be misleading about what is actually going on with the parameters (although the get-help documentation syntax block would expose that regardless).
Accept the $LogFile as a switch argument, then if it's present, just convert it's type to a string value, populating it with the value of the following argument, if present:
param(
[switch]
$LogFile
)
if($LogFile)
{
#We neeed to change the variable type, so remove it
Remove-Variable -Name "LogFile"
#The remaining arguments are placed in $args array.
if($args.Count -gt 0)
{
$LogFile = [String]$args[0]
}
else {
$LogFile = [String]""
}
}
# 1. PS > myFunc
else {
"# 1. PS > myFunc "
}
# 2. PS > myFunc -LogFile
if($LogFile -eq ""){
"LogFile is empty"
}
# 3. PS > myFunc -LogFile "C:\tmp\log.txt"
elseif ($LogFile -is [String]){
"logFile is $logFile"
}
function cookieMonster($ChocolateChipCookie){
if ([string]::IsNullOrEmpty($chocolateChipCookie)){
write-host "Hey wheres my Cookies! "
}
else {
write-host "MMMMM COOKIES! NOM NOM NOM"
}
}
cookieMonster -ChocolateChipCookie C:\cookiejar\cookie.txt
This answer is inspired by the Legendary Mark Wragg above:
function cookieMonster{
param(
[switch] $ChocolateChipCookie,
[string] $Crumbs = $null
)
# The function without parameters
if($ChocolateChipCookie -eq $false){
write-host "no cookies;("
}
# The parameter is not passed
elseif ($ChocolateChipCookie -eq $true -and -not $Crumbs) {
Write-Host "Aw just crumbs!"
}
# The parameter is passed with argument
else {
write-host "MMMMM COOKIES! NOM NOM NOM"
}
}
cookieMonster -ChocolateChipCookie C:\cookiejar\cookie.txt
The $ChocolateChipCookie is defined as a switch parameter.
Then the value given to the switch parameter is created as string parameter called $crumbs.
The if block checks if the function was called without a switch and writes a message.
The elseif block checks if the function was called with a switch which has no value.
The else block writes a message if non of the above conditions are met.

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.