List all previously-loaded PowerShell variables - powershell

Is there a PowerShell command to list all previously loaded variables?
I am running some PowerShell scripts in Visual Studio and would like to list all variables that are available in the current PowerShell session.
I have tried the command:
ls variable:*;
But it returns the following:
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable
System.Management.Automation.PSVariable

ls variable:* should work, or Get-Variable. If these are resulting in bad output, it's due to a poorly-implemented host, not with powershell itself. If you open the standard console host (run powershell.exe), you will see that these work fine.
If you need to work around a bad host, you might have better luck dumping everything to explicit strings:
Get-Variable | Out-String
or
Get-Variable |%{ "Name : {0}`r`nValue: {1}`r`n" -f $_.Name,$_.Value }

Interestingly, you can just type variable, and that works too!
I figured this out because I was curious as to what ls variable:* was doing. Get-Help ls tells us that it's an alias for PowerShell's Get-ChildItem, which I know will list all of the children of an Object. So, I tried just variable, and voila!
Based on this and this, it seems that what ls variable:* is doing is telling it to do some sort of scope/namespace lookup using the * (all/any) wildcard on the variable list, which, in this case, seems extraneous (ls variable:* == ls variable: == variable).

I frequently noodle around, testing concepts in an interactive shell, but sometimes forget my variable names or what-all I have defined.
Here is a function I wrote to wrap Get-Variable, automatically excluding any globals that were defined when the shell started up:
function Get-UserVariable()
{
[CmdletBinding()]
param(
[Parameter(Position = 0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)][String[]]$Name,
[Parameter()][Switch]$ValueOnly,
[Parameter()][String[]]$Include,
[Parameter()][String[]]$Exclude,
[Parameter()][String]$Scope
)
$varNames = $global:PSDefaultVariables + #("PSDefaultVariables")
$gvParams = #{'Scope' = "1"}
if ($PSBoundParameters.ContainsKey('Name'))
{
$gvParams['Name'] = $Name
}
if ($PSBoundParameters.ContainsKey('ValueOnly'))
{
$gvParams['ValueOnly'] = $ValueOnly
}
if ($PSBoundParameters.ContainsKey('Include'))
{
$gvParams['Include'] = $Include
}
if ($PSBoundParameters.ContainsKey('Exclude'))
{
# This is where the magic happens, folks
$gvParams['Exclude'] = ($Exclude + $varNames) | Sort | Get-Unique
}
else
{
$gvParams['Exclude'] = $varNames
}
if ($PSBoundParameters.ContainsKey('Scope'))
{
$gvParams['Scope'] = $Scope
}
gv #gvParams
<#
.SYNOPSIS
Works just like Get-Variable, but automatically excludes the names of default globals.
.DESCRIPTION
Works just like Get-Variable, but automatically excludes the names of default globals, usually captured in the user's profile.ps1 file. Insert the line:
$PSDefaultVariables = (Get-Variable).name |% { PSEscape($_) }
...wherever you want to (either before, or after, any other stuff in profile, depending on whether you want it to be excluded by running this command.)
.PARAMETER Name
(Optional) Refer to help for Get-Variable.
.PARAMETER ValueOnly
(Optional) Refer to help for Get-Variable.
.PARAMETER Include
(Optional) Refer to help for Get-Variable.
.PARAMETER Exclude
(Optional) Refer to help for Get-Variable; any names provided here will be added to the existing list stored in $PSDefaultVariables (sorted / unique'd to eliminate duplicates.)
.PARAMETER Scope
(Optional) Refer to help for Get-Variable. The only asterisk here is that the default value is "1" just to get us out of this function's own scope, but you can override with whatever value you need.
.OUTPUTS
Refer to help for Get-Variable.
.EXAMPLE
PS> $foo = 1,2,3
PS> Get-UserVariable
Name Value
---- -----
foo {1, 2, 3}
#>
}
Set-Alias -Name guv -Value Get-UserVariable
Put the following line somewhere in your profile.ps1 file, either at the very top or very bottom, depending on whether you want to automatically exclude any other variables that get defined every time you run an interactive shell:
# Remember the names of all variables set up to this point (used by Get-UserVariable function)
$PSDefaultVariables = (Get-Variable).name
Hope this is helpful!

Related

how to use hashtable to pass parameters to a powershell cmd that uses double dashes before the parameters

i m writing a script that makes use of a cmdlet from a console app, let's call the cmdlet as cmdA. I have no control over implementation of cmdA. cmdA takes parameters with a double dash (cmdA --param1 value1 --param2 value2 --param3 value3 --param4 value4 --param5 value5)
Now param2, param3, param4 and param5 are optional. The user of my script may or may not provide values for these optional parameters. In the script, i formed a hashtable using the parameters for which the user provided values. Let's call it paramHashtable.
I am executing cmdA #paramHashtable but this is not working. I think it has to do something with double dashes because this approach works fine with a cmd that takes parameters with single dash
What would be an efficient way to pass parameters to the cmdA (an inefficient way would be to use many if blocks to check which values were provided and make calls accordingly)
edited*
This is how i m creating the hashtable :-
$optionalParameters = #{}
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
$requiredParameters = #{"sqlConnectionStrings " = "$SqlConnectionStrings"}
$parameters = $requiredParameters + $optionalParameters
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
$parametersList = #{}
if($outputFolder -ne "")
{
$parametersList["outputFolder "] = $outputFolder
}
if($perfQueryIntervalInSec -ne "")
{
$parametersList["perfQueryIntervalInSec "] = $perfQueryIntervalInSec
}
if($staticQueryIntervalInSec -ne "")
{
$parametersList["staticQueryIntervalInSec "] = $staticQueryIntervalInSec
}
if($numberOfIterations -ne "")
{
$parametersList["numberOfIterations "] = $numberOfIterations
}
return $parametersList
}
This is how i m calling it :-
& $ExePath actionName #parameters
The $ExePath has the path of the program to be executed
The actionName takes parameters like this:-
actionName --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
Can splatting with hash table work on cmdlets / functions where it's parameter have dashes?
It may work, but it is definitely not a good idea to have parameter names with dashes as this will result in a function / cmdlet where named parameters cannot be used, PowerShell binds the arguments positionally! (thanks mklement0 for pointing this out):
function Test-Splatting {
param(${-param1}, ${-param2})
"${-param1} ${-param2}"
}
$param = #{ '--param1' = 'hello'; '--param2' = 'world' }
Test-Splatting #param # => hello world
Example of what was mentioned before using the same function above:
# --param1 is bound positionally and not interpreted as a parameter:
Test-Splatting --param1 hello # => --param1 hello
As for an external programs, the linked answer in comments explains very well the approach you could take using a wrapper function and the use of the automatic variable $args:
function myfunc {
$binaryPath = 'path/to/file.exe'
& $binaryPath actionName $args
# or #args we can't be sure until testing
}
myfunc --sqlConnectionStrings "Data Source=Server1" --outputFolder C:\Output
As noted in the comments, because you are calling an external program, you should use array-based splatting or simply arrays directly to pass programmatically constructed arguments.
By contrast, hashtable-based splatting is usually only helpful when calling PowerShell commands (while it technically works with external programs too, the resulting parameter format (e.g. -foo:bar or -foo:"bar none") is unusual and understood by few external programs)
Note that the parameter names and values must be passed as separate array elements and the elements representing parameter names must include the - or -- prefix; e.g., consecutive array elements
'--sqlConnectionStrings' (the name) and
'Data Source=Server1' (the value).
PowerShell then constructs a command line for invocation of the external program behind the scenes, space-separating the array elements and double-quoting elements with embedded spaces on demand; the above example turns into the following:
--sqlConnectionStrings "Data Source=Server1"
Note that it is up to the target program to parse the single string that is its command line in a way that recognizes parameter name-value pairs.
# Get the array of *optional* parameter names and values from your helper function.
$optionalParameters = ParameterSelection ${OutputFolder} ${PerfQueryIntervalInSec} ${StaticQueryIntervalInSec} ${NumberOfIterations}
# Declare the *required* parameter(s) as an array.
$requiredParameters = '--sqlConnectionStrings', $SqlConnectionStrings
# Pass the arrays as-is to the external program.
# (Empty arrays are effectively ignored.)
& $ExePath actionName $requiredParameters $optionalParmeters
Your ParameterSelection function can be simplified as follows:
function ParameterSelection ([string] $outputFolder, [string] $perfQueryIntervalInSec, [string] $staticQueryIntervalInSec, [string] $numberOfIterations)
{
# Loop over all bound parameters.
$PSBoundParameters.GetEnumerator() | ForEach-Object {
# Emit the parameter name prefixed with '--' and the value.
# PowerShell automatically collects the output strings in an array
# when you assign a call to this function to a variable.
'--' + $_.Key
$_.Value
}
}

How do I read values of a variable of type Map passed from terraform to powershell userdata script?

I need to pass the variable of type map from terraform to powershell userdata script and be able to access the key value pairs of the map in the powershell script. Thank you
userdata.tf
data "template_file" "user_data" {
template = "${file("${path.module}/init.ps1")}"
vars = {
environment = var.env
# I want to pass the values as shown below
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
}
}
init.ps1
$hostnames = "${hostnames}"
$environment = "${environment}"
if ($environment -eq "dev"){
# print the value of the dev key in the hostname map here
}
The template_file data source is discouraged.
Note In Terraform 0.12 and later, the templatefile function offers a built-in mechanism for rendering a template from a file. Use that function instead, unless you are using Terraform 0.11 or earlier.
The templatefile function is preferred which is why my solution uses it instead.
In either case, only map(string) is supported for template vars. The values must be strings. JSON can encode arbitrary tree structures, including your map of hostnames as strings.
In your terraform code, encode your hostnames to JSON with jsonencode.
userdata.tf:
locals {
user_data = templatefile("${path.module}/init.ps1" ,{
environment = var.env
# I want to pass the values as shown below
hostnames = jsonencode({"dev":"devhost","test":"testhost","prod":"prodhost"})
})
}
In your PowerShell, decode your hostnames from JSON with the ConvertFrom-Json cmdlet.
init.ps1:
$hostnames = '${hostnames}' | ConvertFrom-Json
$environment = "${environment}"
if ($environment -eq "dev"){
# print the value of the dev key in the hostname map here
}
Update: As noted in the comments, -AsHashtable won't necessarily work as it was added in PowerShell 6.0. Windows 10 and Windows Server 2016 include PowerShell 5.1. If you have maps with case-only differences in keys ({"name" = "foo" ; "Name" = "bar"}) then you will need to install PowerShell 6.0 or later and use ConvertFrom-Json -AsHashtable.
In order to include a collection value in a template result you must decide how you want to represent it as a string, because template results are always strings.
PowerShell supports JSON encoding via the ConvertFrom-Json cmdlet, so a JSON string might be a good candidate, although it presents some challenges because you must ensure that the JSON string is written into the result as a valid PowerShell expression, which means we must also apply PowerShell escaping.
Putting that all together, you can adjust the template like this:
$hostnames = '${replace(jsonencode(hostnames), "'", "''")}' | ConvertFrom-Json
$environment = '${replace(environment, "'", "''")}'
if ($environment -eq "dev"){
Write-Output $hostnames["dev"]
}
The jsonencode function produces a JSON-encoded version of the given value. The above then passes that result to replace so that any ' characters in the result will be escaped as '', which then allows placing the entire result in single quotes ' to ensure valid PowerShell syntax.
The result of rendering the template would be something like this:
$hostnames = '{"dev":"devhost","test":"testhost","prod":"prodhost"}' | ConvertFrom-Json -AsHashtable
$environment = 'dev'
if ($environment -eq "dev"){
Write-Output $hostnames["dev"]
}
You seem to be using Terraform 0.12, so you should use the templatefile function instead of the template_file data source. The function is better because it can accept values of any type, whereas the data source can only accept string values (because it is designed for Terraform 0.11).
To use templatefile, find the place where you were previously referring to data.template_file.user_data and use the templatefile function there instead:
templatefile("${path.module}/init.ps1", {
environment = var.env
hostnames = {"dev":"devhost","test":"testhost","prod":"prodhost"}
})
You can then remove the data "template_file" "user_data" block, because this templatefile function call replaces it.

Accessing array outside of a PS function

I am having a hard time figuring out how to get the PSCustomObject/array out of the function. I have tried using $Global:ZipList as well as just passing the variables into an array directly w/o a custom object but no luck. The reason I need this, is I need to then loop through the array/list after I get the filenames and then was going to loop through this list and unzip each file and log it and process it based on the extension in the zip; this is to be used for multiple zips, so I can't predetermine the file extensions without grabbing the filenames in the zip into a list. I would just use a shell however some of the zips are password protected, haven't figured out how to pass a password scripted to the shell com unzip windows feature so stuck with 7z for now. Any help would be greatly appreciated! Thanks
Function ReadZipFile([string]$ZipFileName)
{
[string[]]$ReadZipFile = & 'C:\Program Files\7-Zip\7z.exe' l "$ZipFileName"
[bool]$separatorFound = $false
#$ZipList = #()
$ReadZipFile | ForEach-Object{
if ($_.StartsWith("------------------- ----- ------------ ------------"))
{
if ($separatorFound)
{
BREAK # Second separator; We're done!
}
$separatorFound = -not $separatorFound
}
else
{
if ($separatorFound)
{
[DateTime]$FileCreatedDate = [DateTime]::ParseExact($_.Substring(0, 19),"yyyy'-'MM'-'dd HH':'mm':'ss", [CultureInfo]::InvariantCulture)
[Int]$FileSize = [Int]"0$($_.Substring(26, 12).Trim())"
$ZipFileName = $_.Substring(53).TrimEnd()
$ZipList = [PSCustomObject] #{
ZipFileName=$ZipFileName
FileCreatedDate=$FileCreatedDate
FileSize=$FileSize}
}
}
}
}
$z = ReadZipFile $ZipFileName
$ZipList | Select-Object ZipFileName
To be able to select from array created in the function outside of it. I believe my if statements may be blocking the global variable feature when i tried using global:

Powershell arguments list passing like -a <args list> -d <args list>

I want to write a powershell ps1 script, which needs 2 argument sets,(-a -d) and each can have upto n attributes. How to implement that?
example : DoTheTask -a <task name 1> <task name 2> ... -d <machine name 1> <machine name 2>...
You can do this:
param(
[string[]]$a,
[string[]]$d
)
write-host $a
write-host ----
write-host $d
Then you can call DoTheTask -a task1,task2 -d machine1,machine2
Can you organize your task names and machines names in such a way that they can be put in to a single string with delimiters.
In other words, could your -a argument be a string a comma-separated task names and your -d argument be a string of comma-separated machine names? If so, then all you need to do is parse the string into its components at the start of your script.
If you are passing these arguments to the script itself, you could leverage the $args internal variable, though key/value mapping will be a little trickier since PowerShell will interpret each statement as an argument. I suggest (like others) that you use another separator so that you can do the mappings easier.
Nonetheless, if you want to continue doing it this way, you can use a function like the below:
Function Parse-Arguments {
$_args = $script:args # set this to something other than $script:args if you want to use this inside of the script.
$_ret = #{}
foreach ($_arg in $_args) {
if ($_arg.substring(0,1) -eq '-') {
$_key = $_arg; [void]$foreach.moveNext() # set the key (i.e. -a, -b) and moves to the next element in $args, or the tasks to do for that switch
while ($_arg.substring(0,1) -ne '-') { # goes through each task until it hits another switch
$_val = $_arg
switch($_key) {
'-a' {
write-host "doing stuff for $_key"
$ret.add($_key,$_val) # puts the arg entered and tasks to do for that arg.
}
# put more conditionals here
}
}
}
}
}

Parse powershell script parameters

Is there an easy way to parse the params from a powershell script file
param(
[string]$name,
[string]$template
)
I have started reading the file and wondered if there is a better way, maybe by a help/man command?
class PowerShellParameter {
public string Name;
public string Type;
public string Default;
}
string[] lines = File.ReadAllLines(path);
bool inparamblock = false;
for (int i = 0; i < lines.Length; i++) {
if (lines[i].Contains("param")) {
inparamblock = true;
} else if (inparamblock) {
new PowerShellParameter(...)
if (lines[i].Contains(")")) {
break;
}
}
}
There are at least two possibilies. First one (imho better): use Get-Command:
# my test file
#'
param(
$p1,
$p2
)
write-host $p1 $p2
'# | Set-content -path $env:temp\sotest.ps1
(Get-Command $env:temp\sotest.ps1).parameters.keys
For all members look at
Get-Command $env:temp\sotest.ps1 | gm
#or
Get-Command $env:temp\sotest.ps1 | fl *
The other (harder way) is to use regular expression
[regex]::Matches((Get-Help $env:temp\sotest.ps1), '(?<=\[\[-)[\w]+') | select -exp Value
I like the solution with Get-Command proposed by #stej. Unfortunately it does not work if script parameters have explicit types specified and an assembly of such a type is not yet loaded into the session. That is why I still use this script: Get names of script parameters
I'm not really sure what you're after, is it documenting your scripts? In that case have a look at Get-Help about_Comment_Based_Help. It will tell you how to do that, and after that you can use Get-Help on your script/module.
If you're after more strict parameter handling, take a look at about_functions_advanced_parameters and about_functions_cmdletbindings on how to better structure parameters. For example,
[Parameter(Position=0,Mandatory=$true,HelpMessage='Enter
architecture("OSX","WinXP","Win7","Linux")')]
[ValidateSet("OSX","WinXP","Win7","Linux")]
[string]$architecture
will make that parameter mandatory, read from position 0 of the command, allow only a value from the given set, and give a brief help message when asking for input if that parameter was not given.