Array of hash tables not output - powershell

This is a basic question but I'm stuck. I have the below code:
$array = #(
$hashtable1 = #{
Name = "Test1"
Path = "C:\Test1"
}
$hashtable2 = #{
Name = "Test1"
Path = "C:\Test1"
}
)
The array is created but empty. I have tried comma separation:
$hashtable1 = #{}, $hashtable2 = #{}
But this did not work. What is wrong?

You are assigning the hashtables as variables. Take out the variable assignment:
$array = #(
#{
Name = "Test1"
Path = "C:\Test1"
},
#{
Name = "Test1"
Path = "C:\Test1"
}
)

gms0ulman's helpful answer provides an effective solution for constructing your array of hashtables.
To provide some background information:
A variable assignment such as $hashtable1 = ... is not an expression, so it produces no output, which is why your $array = assignment ended up containing an empty array, given that #(...) saw no output.
However, you can make assignment statements produce output simply by enclosing them in (...), which turns them into expressions, which allows you to assign to the variable and output the assigned value.
#(...) is not needed to construct arrays; instead, you can use ,, the array-construction operator.
Even though it may not be needed, the following demonstrates how to both construct the array of hashtables and save the individual hashtables in dedicated variables:
$array =
($hashtable1 = #{
Name = "Test1"
Path = "C:\Test1"
}),
($hashtable2 = #{
Name = "Test1"
Path = "C:\Test1"
})

Related

How to Pass User Input to a JSON File using powershell

I am creating a PowerShell script using Convert To-JSON cmd and i achieved that using below
$body = #{
devices = #(
#{
credentials = #(
#{
label = 'username'
value = 'myname'
sensitive = 'false'
},
#{
label = 'Password'
value = 'Password#!'
sensitive = 'true'
}
)
services = #(
#{
name = 'RDP'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
},
#{
name = 'HTTPS'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
},
#{
name = 'SSH'
url = "https://$inputIpAddress/?pauth=[proxy_token]&data=[connection:$inputUsername]"
instructor = 'false'
}
connections = #(
#{
id = 'myname-rdp'
protocol = 'rdp'
hostname = "192.168.1.6"
port ='3389'
}
)
Parameters = #(
#{
name = 'username'
value = 'myname'
},
#{
name = 'password'
value = 'Password#!'
}
)
}
)
}
i am converting the above powershell to JSON File($body | ConvertTo-Json -Depth 4) and i would like to replace pass the arguments for the Username and IP Address and store it with the username.json every time while converting to JSON.
i have tried the Read-host to get the input from the user but i am facing difficulty to pass that input before printing the output.
For the IP address, you can set the new value by defining the parameter using standard dot-notation.
In the case of username, because Parameters is an array with duplicate object names, doing $body.devices.Parameters.name would return both the username and password objects, so you can filter the array to only select the object where the name is username
$inputUsername = Read-Host "Input Username"
$inputIpAddress = Read-Host "Input IP address"
( $body.devices.Parameters | Where-Object { $_.name -eq 'username' } ).value = $inputUsername
$body.devices.connections.hostname = $inputIpAddress
$body | ConvertTo-Json -Depth 4
As an interpreted language, PowerShell allows you to construct literals based on values that are determined at runtime.
Therefore, simply place your hashtable literal (#{ ... }) after your Read-Host calls, and reference the variables in which you store the Read-Host responses in that literal.
A simplified example:
# Simulate two Read-Host calls (hard-code sample values).
$foo = 42
$bar = 'hi'
# Now define the hashtable literal and reference the variables assigned to above.
# Note: An [ordered] hashtable is used in order to retain the entries
# in definition order.
[ordered] #{
devices = #{
foo = $foo
bar = $bar
}
}
Piping the above to ConvertTo-Json -Depth 4 yields:
{
"devices": {
"foo": 42,
"bar": "hi"
}
}
Note how the values of $foo and $bar were incorporated. You could even use the same technique in a loop, provided the hashtable literal is also in the loop body (after setting the variables it references).

How do I reverse transversal in a PowerShell array

Given a PowerShell array hashtable1 similar to the following:
$dept= #{
'Sales' = #{
'SAM' = 'Manager'
'SAP' = 'Person'
}
'IT' = #{
'ITM' = 'Manager'
'ITS' = 'Specialist'
'ITT' = 'Technician'
'ITC' = 'Consultant'
}
}
If enter the following in the console:
$dept.it.itc
$dept.sales.sam
I get:
Consultant
Manager
Which is as expected.
However, what I'd like to do is something like
write-host $dept.itc
write-host $dept.sam
and get
IT Consultant
Sales Manager
in return.
I'm looking for a sort function to do a 'reverse traversal' of the array because 'IT', 'Sales' etc are the OU's I need to put new users into. There are many more OU's that I have removed for brevity.
[1] An array is simply a list of values and a hashtable is a collection of key/value pairs similiar to Javascript's JSON or Python's dict.
It is worth noting that your object is not an Array. In PowerShell #{} is a Hashtable. You can read more about working with Hashtables here.
If you have what I am going to call a unique Role Code for each role in your department OU's, all you want to do is match the Key in the nested Hashtables to find your department. It's easiest to create a quick helper function to deal with multiple calls, unless you are just looping through an array or list of strings.
Here is an example of how to extract the string you want: (If you do not have unique keys, then you may need to add additional filtering)
$Departments = #{
'Sales' = #{
'SAM' = 'Manager'
'SAP' = 'Person'
}
'IT' = #{
'ITM' = 'Manager'
'ITS' = 'Specialist'
'ITT' = 'Technician'
'ITC' = 'Consultant'
}
}
function Get-DepartmentOU {
Param (
[CmdletBinding()]
[Parameter(Mandatory = $true)]
[System.String]
$RoleCode
)
# Get the DictionaryEntry in the main Hashtable where the nested Hashtable value matches the role you are looking for.
$Department = $script:Departments.GetEnumerator() | Where-Object { $_.Value.ContainsKey($RoleCode) }
# Print the name of the DictionaryEntry (Your department) and retrieve the value from the Hashtable for the role.
Write-Output ("{0} {1}" -f $Department.Name, $Department.Value[$RoleCode])
}
And then you can get them by running the function and specifying the code.
PS > Get-DepartmentOU -RoleCode ITC
IT Consultant

What is '#{}' meaning in PowerShell

I have line of scripts for review here, I noticed variable declaration with a value:
function readConfig {
Param([string]$fileName)
$config = #{}
Get-Content $fileName | Where-Object {
$_ -like '*=*'
} | ForEach-Object {
$key, $value = $_ -split '\s*=\s*', 2
$config[$key] = $value
}
return $config
}
I wonder what #{} means in $config = #{}?
#{} in PowerShell defines a hashtable, a data structure for mapping unique keys to values (in other languages this data structure is called "dictionary" or "associative array").
#{} on its own defines an empty hashtable, that can then be filled with values, e.g. like this:
$h = #{}
$h['a'] = 'foo'
$h['b'] = 'bar'
Hashtables can also be defined with their content already present:
$h = #{
'a' = 'foo'
'b' = 'bar'
}
Note, however, that when you see similar notation in PowerShell output, e.g. like this:
abc: 23
def: #{"a"="foo";"b"="bar"}
that is usually not a hashtable, but the string representation of a custom object.
The meaning of the #{}
can be seen in diffrent ways.
If the #{} is empty, an empty hash table is defined.
But if there is something between the curly brackets it can be used in a contex of an splatting operation.
Hash Table
Splatting
I think there is no need in explaining what an hash table is.
Splatting is a method of passing a collection of parameter values to a command as unit.
$prints = #{
Name = "John Doe"
Age = 18
Haircolor = "Red"
}
Write-Host #prints
Hope it helps! BR
Edit:
Regarding the updated code from the questioner the answer is
It defines an empty hash table.
Be aware that Get-Content has its own parameters!
THE MOST IMPORTANT 1:
[-Raw]

Creating a reference to a hash table element

So, I'm trying to create a tree-type variable that I could use for data navigation. I've ran into an issue while trying to use reference variables on hash tables in PowerShell. Consider the following code:
$Tree = #{ TextValue = "main"; Children = #() }
$Item = #{ TextValue = "sub"; Children = #() }
$Pointer = [ref] $Tree.Children
$Pointer.Value += $Item
$Tree
When checking reference variable $Pointer, it shows appropriate values, but main variable $Tree is not affected. Is there no way to create references to a hash table element in PowerShell, and I'll have to switch to a 2-dimensional array?
Edit with more info:
I've accepted Mathias' answer, as using List looks like exactly what I need, but there's a little more clarity needed on how arrays and references interact. Try this code:
$Tree1 = #()
$Pointer = $Tree1
$Pointer += 1
Write-Host "tree1 is " $Tree1
$Tree2 = #()
$Pointer = [ref] $Tree2
$Pointer.Value += 1
Write-Host "tree2 is " $Tree2
As you can see from the output, it is possible to get a reference to an array and then modify the size of the array via that reference. I thought it would also work if an array is an element of another array or a hash table, but it does not. PowerShell seems to handle those differently.
I suspect this to be an unfortunate side-effect of the way += works on arrays.
When you use += on a fixed-size array, PowerShell replaces the original array with a new (and bigger) array. We can verify that $Pointer.Value no longer references the same array with GetHashCode():
PS C:\> $Tree = #{ Children = #() }
PS C:\> $Pointer = [ref]$Tree.Children
PS C:\> $Tree.Children.GetHashCode() -eq $Pointer.Value.GetHashCode()
True
PS C:\> $Pointer.Value += "Anything"
PS C:\> $Tree.Children.GetHashCode() -eq $Pointer.Value.GetHashCode()
False
One way of going about this is to avoid using #() and +=.
You could use a List type instead:
$Tree = #{ TextValue = "main"; Children = New-Object System.Collections.Generic.List[psobject] }
$Item = #{ TextValue = "sub"; Children = New-Object System.Collections.Generic.List[psobject] }
$Pointer = [ref] $Tree.Children
$Pointer.Value.Add($Item)
$Tree
To complement Mathias R. Jessen's helpful answer:
Indeed, any array is of fixed size and cannot be extended in place (#() creates an empty [object[]] array).
+= in PowerShell quietly creates a new array, with a copy of all the original elements plus the new one(s), and assigns that to the LHS.
Your use of [ref] is pointless, because $Pointer = $Tree.Children alone is sufficient to copy the reference to the array stored in $Tree.Children.
See bottom section for a discussion of appropriate uses of [ref].
Thus, both $Tree.Children and $Pointer would then contain a reference to the same array, just as $Pointer.Value does in your [ref]-based approach.
Because += creates a new array, however, whatever is on the LHS - be it $Pointer.Value or, without [ref], just $Pointer - simply receives a new reference to the new array, whereas $Tree.Children still points to the old one.
You can verify this by using the direct way to determine whether two variables or expressions "point" to the same instance of a reference type (which all collections are):
PS> [object]::ReferenceEquals($Pointer.Value, $Tree.Children)
False
Note that [object]::ReferenceEquals() is only applicable to reference types, not value types - variables containing the latter store values directly instead of referencing data stored elsewhere.
Mathias' approach solves your problem by using a [List`1] instance instead of an array, which can be extended in place with its .Add() method, so that the reference stored in $Pointer[.Value] never needs to change and continues to refer to the same list as $Tree.Children.
Regarding your follow-up question: appropriate uses of [ref]:
$Tree2 = #()
$Pointer = [ref] $Tree2
In this case, because [ref] is applied to a variable - as designed - it creates an effective variable alias: $Pointer.Value keeps pointing to whatever $Tree2 contains even if different data is assigned to $Tree2 later (irrespective of whether that data is a value-type or reference-type instance):
PS> $Tree2 = 'Now I am a string.'; $Pointer.Value
Now I am a string.
Also note that the typical [ref] use case is to pass variables to functions to .NET API methods that have ref or out parameters; while you can use it with PowerShell scripts and functions too in order to pass by-reference parameters, as shown in the following example, this is best avoided:
# Works, but best avoided in PowerShell code.
PS> function foo { param([ref] $vRef) ++$vRef.Value }; $v=1; foo ([ref] $v); $v
2 # value of $v was incremented via $vRef.Value
By contrast, you cannot use [ref] to create such a persistent indirect reference to data, such as the property of an object contained in a variable, and use of [ref] is essentially pointless there:
$Tree2 = #{ prop = 'initial val' }
$Pointer = [ref] $Tree2.prop # [ref] is pointless here
Later changing $Tree2.prop is not reflected in $Pointer.Value, because $Pointer.Value statically refers to the reference originally stored in $Tree2.prop:
PS> $Tree2.prop = 'later val'; $Pointer.Value
initial val # $Pointer.Value still points to the *original* data
PowerShell should arguably prevent use of [ref] with anything that is not a variable. However, there is a legitimate - albeit exotic - "off-label" use for [ref], for facilitating updating values in the caller's scope from descendant scopes, as shown in the conceptual about_Ref help topic.
You can create the pointer into your tree structure:
$Tree = #{ TextValue = "main"; Children = [ref]#() }
$Item1 = #{ TextValue = "sub1"; Children = [ref]#() }
$Item2 = #{ TextValue = "sub2"; Children = [ref]#() }
$Item3 = #{ TextValue = "subsub"; Children = [ref]#() }
$Pointer = $Tree.Children
$Pointer.Value += $Item1
$Pointer.Value += $Item2
$Pointer.Value.Get(0).Children.Value += $Item3
function Show-Tree {
param ( [hashtable] $Tree )
Write-Host $Tree.TextValue
if ($Tree.Children.Value.Count -ne 0) {
$Tree.Children.Value | ForEach-Object { Show-Tree $_ }
}
}
Show-Tree $Tree
Output:
main
sub1
subsub
sub2

Calling functions with maps using #

I've come across some syntax that I cannot understand. Here is a sample
$someList = #( <# ... #> )
$name = "someString"
$myMap = #{
Name = $name
Foo = 15
}
Invoke-MyFoo #myMap #someList
What is the meaning #-signs in the Invoke-MyFoo expression? What does it do?
It's a technique known as splatting. It lets you pass a set of parameters as a hashtable or array instead of specifying them all with the cmdlet.
If you're asking a more basic question, then #( ) identifies an array, and #{ } identifies a hashtable.