I have a powershell script that with the following hashtable
[HashTable]$folder_and_prefix = #{}
After a certain point in my script I no longer need that hashtable, I tried doing:
Remove-Variable $folder_and_prefix
but I get the following error:
Remove-Variable : Cannot find a variable with the name
'System.Collections.Hashtable'
Is it possible to remove a hastable that I no longer need?
This is a common mistake. Remove-Variable is taking the name of the variable (a [String]), and by referencing the variable itself (with a dollar sign $) you are passing the value. Remove the dollar sign and that's all you need:
Remove-Variable folder_and_prefix
Further, it takes an array of names, so you can do:
$var1 = 5
$var2 = 'Hello'
$var3 = #{}
Remove-Variable var1,var2,var3
And it accepts wildcards:
Remove-Variable var*
The wildcard acceptance is also true for Set-Variable, Get-Variable, and Clear-Variable (New-Variable is the exception).
As mentioned by others, don't use the dollar sign ($) when referring to the variable itself. Additionally, I always check to see if variable exists (before attempting to delete it) using conditional statement as below:
if (Test-Path variable:myVar) {Remove-Variable myVar} #Delete only if local variable exists.
[int]$myVar = 5;
You can also check your variable's existence in a specific Scopes, such as, for Global scope:
if (Test-Path variable:global:myVar) {Remove-Variable myVar –Scope Global}
You can even check Environment variables as below:
if (Test-Path env:ComputerName) {"do something with $env:ComputerName."}
HTH
Related
Without going much into details on why am I even trying this out, is it possible to set PSDefaultParameterValues conditionally based on other parameter values?
Let's say I would like to set -Force if ItemType is Directory in New-Item call.
$PSDefaultParameterValues = #{ "New-Item:Force" = {
# TODO: if Itemtype is Directory, return $true
# else return default: false
return $false
}
}
New-Item -ItemType Directory
Problem is, that I can get the parameters used in $args but I do not have access to their values.
As you've observed, the argument passed to your script block via the automatic $args variable contains the names of the bound parameters in the New-Item call at hand, but lacks their values.
This looks like an oversight, which GitHub proposal #16011 aims to correct.
The following workaround isn't foolproof, but may suffice in practice:
$PSDefaultParameterValues = #{
'New-Item:Force' = {
($false, $true)[
$args.BoundParameters.Contains('ItemType') -and
(Get-PSCallStack)[1].Position.Text -match '\bDirectory\b'
]
}
}
You could tweak the regex to be stricter, but note that PowerShell's elastic syntax and parameter aliases make it hard to match a parameter name reliably; e.g., -Type Directory, -it Directory and -ty Directory are all acceptable variations of -ItemType Directory.
A caveat is that this won't work if you pass the Directory argument to -ItemType in New-Item calls via a variable; e.g., $type='Directory'; New-Item -ItemType $type ... would not be recognized by the script block. Handling that case would require substantially more work.
Note:
The parent call-stack entry, which you can obtain as the 2nd element of the call-stack array returned by Get-PSCallStack, contains the raw command text of the New-Item call at hand (in property .Position.Text), which the solution above examines.
However, since it is the raw command text, it doesn't include the expanded argument values that are ultimately seen by the command; that is, what variable references and expression evaluate to isn't directly available.
You could perform your own expansion, assuming you've reliably identified the variable reference / subexpression of interest, but note that, at least in principle, evaluating a subexpression can have side effects (and possibly also take a long time to execute), so effectively executing it twice may be undesirable.
I have come across the strangest behaviour that has been driving me nuts when writing scripts. It is impossible sometimes to remove the value of a variable in Powershell. I have tried:
Remove-Variable -Force
Also tried making it equal to an empty string or making it $null but the variable value and type remains.
Anyone have an idea how this can happen?
I am using Powershell version 5 on Windows Server 2016.
Here some screenshots:
To remove a variable, pass its name without the $ sigil to the Remove-Variable cmdlet's
-Name parameter (which is positionally implied); using the example of a variable $date:
Using an argument:
# Note the required absence of $ in the name; quoting the var. name is
# optional in this case.
Remove-Variable -Force -Name date
Using the pipeline would require you to specify objects whose .Name property contains the name of the variable to delete, because these property values implicitly bind to Remove-Variable's -Name parameter; the simplest way to achieve that is to use the Get-Variable cmdlet, which too requires specifying the name without the $:
# Works, but is inefficient.
Get-Variable -Name date | Remove-Variable -Force
However, this is both more verbose and less efficient than directly passing the name(s) as an argument.
As for what you tried:
You variable-removal command is conceptually flawed:
$date | Remove-Variable -Force
Except as the LHS of an assignment ($date = ...), referring to a variable with the $ sigil returns its value, not the variable itself.
That is, since your $date variable contains a [datetime] instance, it is that instance that is sent through the pipeline, and since only strings are supported as input - that is, variable names - the command fails.
In effect, your call is equivalent to the following, which predictably fails:
PS> Get-Date | Remove-Variable -Force
Remove-Variable : 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 match any of the parameters that take pipeline input.
What the somewhat verbose, general error message is implying in this case is that the input object was of the wrong type (because only objects with a .Name property are accepted, which [datetime] doesn't have).
Contexts in which you need refer to a variable itself rather than to its value:
What these contexts have in common is that you need to specify the variable name without the $ sigil.
Two notable examples:
All *-Variable cmdlets expect the names of variables to operate on, such as the Get-Variable cmdlet that returns objects representing variables, of type System.Management.Automation.PSVariable; these objects include the name, value, and other attributes of a PowerShell variable.
# Gets an object describing variable $date
$varObject = Get-Variable date # -Name parameter implied
When you pass the name of an output variable to a -*Variable common parameter
# Prints Get-Date's output while also capturing the output
# in variable $date.
Get-Date -OutVariable date
As implied, above, assigning to a variable with = is the only exception: there you do use the $ sigil, e.g. $date = Get-Date.
Note that this differs from POSIX-compatible shells such as bash, where you do not use $ in assignments (and must not have whitespace around =); e.g., date=$(date).
I'm quite new to Powershell scripting and I have hit a bump where I obviously don't know how to ask Google the right question.
I am writing a script to be called from a system that only allows me to add a single parameter to the command line, but I in fact need more values to execute the script.
My idea is to build a variable for each possible parameter, and then use the variable going forward (Simplified):
$name1= "value1","value2","value3"
$name2= "value4","value5","value6"
$name3= "value7","vlaue8","value9"
foreach ($value in $nameX) { }
and then call the script like: script.ps1 nameX
But how to convert the parameter into the name of the corresponding variable?
Or are there easier ways...?
You should be able to solve your problem with the Get-Variable cmdlet:
# The one and only argument passed: the name of a variable defined inside
# the script; e.g., 'name1'
$variableName = $args[0]
# Define the variables that the argument can refer to:
$name1= "value1","value2","value3"
$name2= "value4","value5","value6"
$name3= "value7","vlaue8","value9"
# Use Get-Variable to get a variable's value by name.
# (Error handling omitted for brevity.)
foreach ($value in (Get-Variable $variableName -ValueOnly)) {
# ...
}
Is there a way to get only the locally declared variables in a powershell script?
In this snippit, I would want it to return only myVar1, myVar2, anotherVar:
$myVar1 = "myVar1"
$myVar2 = "myVar2"
$anotherVar = "anotherVar"
Get-Variable -Scope Script
But it instead returns a ton of other local script variables.
The problem I'm trying to solve, and maybe you can suggest another way, is that I have many Powershell scripts that have a bunch of misc variable constants declared at the top.
I want to export them all to disk (xml) for import later.
So to call Get-Variable bla* | Export-Clixml vars.xml, I need to know all of the variable names.
So is there a way I can like do
$allVars = {
$myVar1 = "alex"
$myVar2 = "iscool"
$anotherVar = "thisisanotherVar"
}
Get-Variable allVars | Export-Clixml "C:\TEMP\AllVars.xml"
And then later Import-Clixml .\AllVars.xml | %{ Set-Variable $_.Name $_.Value } ?
So that the rest of the script could still use $myVar1 etc without major changes to what is already written?
The issue is there are more variables that are accessible in that scope beyond the ones you already declared. One thing you could do is get the list of variables before you declare yours. Get another copy of all the variables and compare the list to just get yours.
$before = Get-Variable -Scope Local
$r = "Stuff"
$after = Get-Variable -Scope Local
# Get the differences
Compare-Object -Reference $current -Difference $more -Property Name -PassThru
The above should spit out names and simple values for your variables. If need be you should be able to easily send that down the pipe to Export-CliXML. If your variables are complicated you might need to change the -depth for more complicated objects.
Caveat: If you are changing some default variable values the above code currently would omit them since it is just looking for new names.
Also not sure if you can import them exactly in the same means as they were exported. This is largely dependent on your data types. For simple variables this would be just fine
I need to know all of the variable names.
The only other way that I am aware of (I never really considered this) would be to change all of the variable to have a prefix like my_ so then you could just have one line for export.
Get-Variable my_* | Export-Clixml vars.xml
I have two PowerShell scripts.
The first script has the following code:
$var = "abc"
$DIR = "C:\"
$SCRIPT_NAME = "abc.ps1"
&"${DIR}\${SCRIPT_NAME}" #execute the second script
If I want to pass the variable $var to the second script, how do I achieve that? What code do I need to put in both the first and the second script?
Parameters (Recommended): Use parameters to pass values to the second script.
Step2.ps1:
param ($myparameter)
write-host $myparameter
Step1.ps1:
$var = "abc"
$DIR = "C:\"
$SCRIPT_NAME = "step2.ps1"
&"${DIR}\${SCRIPT_NAME}" -myparameter $var
Alternative: You could also have used arguments $args (extra values not linked to a parameter). You can specify the first argument using $args[0]. I would however always recommend parameters as arguments needs to be in a specific order (if multiple arguments are passed) etc.
Step2.ps1:
write-host $args[0]
Step1.ps1:
$var = "abc"
$DIR = "C:\"
$SCRIPT_NAME = "step2.ps1"
&"${DIR}\${SCRIPT_NAME}" $var
There are several ways to do what you want, two of which have already been suggested by #FrodeF..
Pass the variable as a (named) parameter:
# script1.ps1
$var = 'foo'
$dir = 'C:\some\folder'
$scriptname = "script2.ps1"
& "${dir}\${scriptname}" -Foo $var
# script2.ps1
Param($foo)
Write-Output $foo
This is the cleanest solution. You have a well-defined interface and pass the variable in a clear-cut way from one script to another.
Parameter definitions will also allow you to make a parameter mandatory (so that the script will ask the user to provide input if the parameter was omitted), require a particular data type, easily incorporate validation routines, or add comment-based help.
# script2.ps1
<#
.SYNOPSIS
Short description of the script or function.
.DESCRIPTION
Longer description of what the script or function actually does.
.PARAMETER Foo
Description of the parameter Foo.
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[ValidateRange(2,42)]
[int]$foo
)
Write-Output $foo
See Get-Help about_Function_Advanced_Parameters for more information.
Pass the variable as an unnamed argument:
# script1.ps1
$var = 'foo'
$dir = 'C:\some\folder'
$scriptname = "script2.ps1"
& "${dir}\${scriptname}" $var
# script2.ps1
Write-Output $args[0]
This is the second best approach, because you still pass the variable in a clear-cut way, but the interface isn't as well defined as before.
Define the variable as an environment variable:
# script1.ps1
$env:var = 'foo'
$dir = 'C:\some\folder'
$scriptname = "script2.ps1"
& "${dir}\${scriptname}"
# script2.ps1
Write-Output $env:var
This is a less clean approach than the argument-based ones, as the variable is passed using a "side-channel" (the process environment, which is inherited by child processes).
Just define the variable in the first script and use it in the second one:
# script1.ps1
$var = 'foo'
$dir = 'C:\some\folder'
$scriptname = "script2.ps1"
& "${dir}\${scriptname}"
# script2.ps1
Write-Output $var
This will work as well, because by using the call operator (&) the second script is run in the same context as the first script and thus has access to the same variables. However, "passing" a variable like this will easily break if someone runs the second script in a different context/scope or modies it without being aware of the implicit dependency.
If you want to go this route it's usually better to use the first script for variable (and function) definitions only, and dot-source it in the second script, so that the definitions are imported into the scope of the second script:
# script1.ps1
$var = 'foo'
# script2.ps1
. 'C:\path\to\script1.ps1'
Write-Output $var
Technically, passing values via a file would be another option. However, I would recommend against using this approach for several reasons:
it's prone to errors due to improper permissions (could be mitigated by creating the file in the $env:TEMP folder),
it's prone to littering the filesystem if you don't clean up the file afterwards,
it needlessly generates disk I/O when simple in-memory operations provided by the language would suffice.