A null key is not allowed in a hash literal - powershell

I would like to be able to save this hashtable in a variable called: $arr.
As you can see we define two variables $displayName and $typekind and I put them in the hashtable.
But when i try to run the script it gives me an error:
InvalidOperation: A null key is not allowed in a hash literal.
I have tried some different things but i am not sure where error is happening. Hope you can help me
...
if (!( $resourceTypes -like ("*" + $resource.type + "*")) -and ($azDiagSetting)) {
$displayName = $resource.type.replace('/', ' ').Split(' ')[-1]
$typekind = $resource.type
$arr = #{
type = "Microsoft.Authorization/policyDefinitions"
apiVersion = "2020-09-01"
properties = #{
metadata = #{
category = "Monitoring"
}
description = "This policy automatically deploys and enable diagnostic settings to Log Analytics"
displayName = "Apply diagnostic settings for $($displayName) - Log Analytics"
policyRule = #{
then = #{
details = #{
roleDefinitionIds = #(
"/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293"
)
name = "setByPolicy"
type = "Microsoft.Insights/diagnosticSettings"
existenceCondition = #{
AllOf = #(
#{
matchInsensitively = "[[parameters('logAnalytics')]"
field = "Microsoft.Insights/diagnosticSettings/workspaceId"
}
)
}
deployment = #{
properties = #{
template = #{
contentVersion = "1.0.0.0"
resources = #(
#{
type = "$($typekind)/providers/diagnosticSettings"
apiVersion = "2021-05-01-preview"
properties = #{
metrics = #(
#{ }
)
workspaceId = "[[parameters('logAnalytics')]"
logs = #(
#{}
)
}
location = "[[parameters('location')]"
name = "[[concat(parameters('resourceName'), '/', 'Microsoft.Insights/setByPolicy')]"
}
)
parameters = #{
location = #{
type = "string"
}
logAnalytics = #{
type = "string"
}
resourceName = #{
type = "string"
}
}
$schema = "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"
}
parameters = #{
location = #{
value = "[[field('location')]"
}
logAnalytics = #{
value = "[[parameters('logAnalytics')]"
}
resourceName = #{
value = "[[field('name')]"
}
}
mode = "incremental"
}
}
}
}
}
}
} | ConvertTo-Json -Depth 20
$arr
}

PowerShell is attempting to assign the value of $schema (a variable) as a Key of your hash table. As it seems and could be the only possible explanation for your error, this variable hasn't been defined hence is effectively $null:
$schema = $null
$hash = #{
$schema = 'hello'
}
# Above errors with the exception message:
# "A null key is not allowed in a hash literal."
If you want to assign $schema as the literal name of your Key, you can use single-quoted strings:
A string enclosed in single-quotation marks is a verbatim string. The string is passed to the command exactly as you type it. No substitution is performed.
$hash = #{
'$schema' = 'hello'
}
# Results in:
Name Value
---- -----
$schema hello

Related

I am unable to get splatting to work with New-ADUser using given and created attributes

I am processing an array of AD User data pulled from one domain to recreate in another. I have created a hash table linking the New-ADUser parameters with the user data imported from a CSV (populated from the domain I intend to recreate). When I call New-ADUser with the hash table, the user is not created and there are no error.
Here is the hash table:
$NewUserAttr = #{
'Name' = $ADUser.UsersName
'SamAccountName' = $ADUser.UsersSamAccountName
'Company' = $ADUser.UsersCompany
'Department' = $ADUser.UsersDepartment
'DisplayName' = $ADUser.UsersDisplayName
'EmailAddress' = $ADUser.UsersMail
'EmployeeID' = $ADUser.UsersEmployeeID
'Enabled' = $UsersEnabled
'GivenName' = $ADUser.UsersGivenName
'Initials' = $ADUser.UsersInitials
'Manager' = $ADUser.Manager
'MobilePhone' = $ADUser.UsersMobileNum
'OfficePhone' = $ADUser.UsersTelephoneNumber
'PostalCode' = $ADUser.UsersPostalCode
'State' = $ADUser.UsersST
'StreetAddress' = $ADUser.UsersStreetAddress
'Surname' = $ADUser.UsersSN
'Title' = $ADUser.UsersTitle
'userPrincipalname' = $ADUser.UsersUPN
'Path' = $ParentOU
'Server' = $TargetDomain
'OtherAttr' = #{
'c' = $ADUser.Usersc
'GIDNumber' = $ADUser.UsersGIDNumber
'l' = $ADUser.UsersL
'LoginShell' = $ADUser.UsersLoginShell
'msSFU30Name' = $ADUser.UsersMsSFU30Name
'msSFU30NisDomain' = $ADUser.UsersMsSFU30NisDomain
'PhysicalDeliveryOfficeName' = $ADUser.UsersPhysicalDeliveryOfficeName
'SSN' = $ADUser.UsersSSN
'Uid' = $ADUser.UsersUid
'uidNumber' = $ADUser.UsersUidNum
'unixHomeDirectory' = $ADUser.UsersUHD
}
}
PS > New-ADUser #NewUserAttr
I have reduced the NewUserAttr to Name, SamAccountName, Path, and Server and that did create the user, but that is far less parameters than what I need.
Continuing from my comments:
To avoid empty fields from being assembled in the attributes Hashtable to use for splatting, you could create two lookup tables in which you map the CSV header names with the actual AD user attribute names.
Something like this:
# create two mapping lookup Hashtables for 'normal' attributes and one for the OtherAttributes
# format is: PropertyNameFromCsv = PropertyNameForActiveDirectory
$attribMap = #{
UsersName = 'Name'
UsersSamAccountName = 'SamAccountName'
UsersCompany = 'Company'
Usersc = 'Country'
Manager = 'Manager'
# etc.
}
$otherMap = #{
UsersGIDNumber = 'GIDNumber'
UsersLoginShell = 'LoginShell'
UsersmsSFU30Name = 'MsSFU30Name'
UsersmsSFU30NisDomain = 'MsSFU30NisDomain'
# etc.
}
Next, import the CSV and loop over each entry:
$csv = Import-Csv -Path 'X:\your_importFile.csv'
foreach ($item in $csv) {
# two empty Hashtables for splatting
$NewUserAttr = #{}
$OtherAttr = #{}
# pre fill the default attributes you need for all users
$NewUserAttr['Enabled'] = $UsersEnabled
$NewUserAttr['Server'] = $TargetDomain
$NewUserAttr['Path'] = $ParentOU
# loop over the properties for each item in the CSV and only store
# the ones that are not empty and for which you can find a mapping
$item.PsObject.Properties | ForEach-Object {
if (![string]::IsNullOrWhiteSpace($_.Value)) {
if ($attribMap.Contains($_.Name)) { $NewUserAttr[$attribMap[$_.Name]] = $_.Value }
elseif ($otherMap.Contains($_.Name)) { $OtherAttr[$otherMap[$_.Name]] = $_.Value }
}
}
# join the hashtables together if we have OtherAttributes
if ($OtherAttr.Count) { $NewUserAttr['OtherAttributes'] = $OtherAttr }
# now try and create the new user
try {
New-ADUser #NewUserAttr -ErrorAction Stop
}
catch {
Write-Warning "Error creating user $($NewUserAttr.Name): $($_.Exception.Message)"
}
}
I run into this often with named parameters and null values. To avoid null values for your named parameters which $null indicates the parameter should be omitted rather than provided, follow these rules when creating the splat-hashtable:
When defining the hashtable, define any properties that will not (or should not be) $null, like so:
$splatArgs = #{
ParameterOne = 'Value'
ParameterTwo = 'OtherValue'
}
For parameters that may or may not need to be provided based on whether its value is $null (or evaluates falsey), conditionally add it to the hashtable:
if( $someVar ) {
$splatArgs.ConditionalParameter = $someVar
}
Repeat 2. for each conditional argument you have. Alternatively, you could initialize the hashtable with all possible parameter names and values, then strip them out after after checking falsiness, comparing to $null, etc.
# You could do any condition here but in this example,
# We will do a simple falsiness test on each key's value
$removeParameterNames = $splatArgs.Keys | Where-Object {
!( $splatArgs.$_ )
}
# Use another loop here since we don't want to modify the hashtable
# while iterating over its keys
foreach( $key in $removeParameterNames ) {
$splatArgs.Remove($key)
}
Here is what I ended up getting to work, which is very similar to what Bender recommended and what Richard from the link in my above comments recommended.
$NewUserAttr = #{
'Name' = $ADUser.UsersName
'SamAccountName' = $ADUser.UsersSamAccountName
'AccountPassword' = (ConvertTo-SecureString -AsPlainText "<Ab#1Cd!2>" -Force)
'Company' = $ADUser.UsersCompany
'Department' = $ADUser.UsersDepartment
'DisplayName' = $ADUser.UsersDisplayName
'EmailAddress' = $ADUser.UsersMail
'EmployeeID' = $ADUser.UsersEmployeeID
'Enabled' = $UsersEnabled
'GivenName' = $ADUser.UsersGivenName
'MobilePhone' = $ADUser.UsersMobileNum
'OfficePhone' = $ADUser.UsersTelephoneNumber
'SurName' = $ADUser.UsersSN
'Title' = $ADUser.UsersTitle
'userPrincipalname' = $ADUser.UsersUPN
'Path' = $ParentOU
'Server' = $TargetDomain
'OtherAttr' = #{
'GIDNumber' = $ADUser.UsersGIDNumber
'LoginShell' = $ADUser.UsersLoginShell
'msSFU30Name' = $ADUser.UsersMsSFU30Name
'msSFU30NisDomain' = $ADUser.UsersMsSFU30NisDomain
'Uid' = $ADUser.UsersUid
'uidNumber' = $ADUser.UsersUidNum
'unixHomeDirectory' = $ADUser.UsersUHD
}
}
# Check the uncommon attributes and add them to the hash table if not null
if($ADUser.Usersl){$NewUserAttr.add('City',$ADUser.Usersl)}
if($ADUser.Usersc){$NewUserAttr.add('Country',$ADUser.Usersc)}
if($ADUser.UsersInitials){$NewUserAttr.add('Initials',$ADUser.UsersInitials)}
if($ADUser.Manager){$NewUserAttr.add('Manager',$ADUser.Manager)}
if($ADUser.UsersPostalCode){$NewUserAttr.add('PostalCode',$ADUser.UsersPostalCode)}
if($ADUser.UsersST){$NewUserAttr.add('State',$ADUser.UsersST)}
if($ADUser.UsersStreetAddress){$NewUserAttr.add('StreetAddress',$ADUser.UsersStreetAddress)}
if($ADUser.physicaldeliveryofficename){$NewUserAttr.OtherAttr.add('physicaldeliveryofficename',$ADUser.physicaldeliveryofficename)}
if($ADUser.UsersSSN){$NewUserAttr.OtherAttr.add('SSN',$ADUser.UsersSSN)}
#Add new user to destination domain
try {
$UserExists = Get-ADUser -Identity $ADUser.UsersName -Server $TargetDomain
if ($UserExists) {
"Exists,$($ADUser.UsersName),$ParentOU`n" | Out-File $UserCreationLog -Append -Force
} else {
New-ADUser #NewUserAttr -ErrorAction Continue
#Change password
"Added,$($ADUser.UsersSamAccountName),$($ADUser.UsersName),$ParentOU`n" | Out-File $UserCreationLog -Append -Force
}
} catch {
$ErrorMsg = $_.Exception.Message
Write-Log -Message "Unable to create user, $($ADUser.UsersName): $ErrorMsg." -Severity Error -LogPath $LogFile
Write-Log -Message "Failed users attributes: $NewUserAttr $($NewUserAttr.OtherAttr)" -Severity Error -LogPath $LogPath
}
Now I just need to test each of these suggest answers to see which is the fastest! Thanks everyone!!

Change nested object values in a powershell object

I have the following object, and I would like to change a nested value in the object without changing all the other nested objects. The object I would like to change is in a variable.
$object.Tophashtable.MidHashtable1.array1[0].linenumber = 1
Value = Bottom-array1-1
array1[1].linenumber = 2
Value = Bottom-array1-2
array1[2].linenumber = 3
Value = Bottom-array1-3
array2[0].linenumber = 1
Value = Bottom-array2-1
array2[1].linenumber = 2
Value = Bottom-array2-2
MidHashtable2.array3[0].linenumber = 1
Value = Bottom-array3-1
array3[1].linenumber = 2
Value = Bottom-array3-2
array3[2].linenumber = 3
Value = Bottom-array3-3
array4[0].linenumber = 1
Value = Bottom-array4-1
array4[1].linenumber = 2
Value = Bottom-array4-2
MidHashtable3.array5[0].linenumber = 1
Value = Bottom-array5-1
I would like to do the following:
$newobject = #{
linenumber = "1"
value = "newobject-1"}
$object.$change = $newobject
I'm able to read the content of the subobject with the following code:
iex "`$object.$change"
But I can't find a way to set/change the subobject.
code example:
$object = #{
tophashtable = #{
MidHashtable1 = #{
array1 = #(
#{
linenumber = "1"
value = "Bottum-array1-1"
},
#{
linenumber = "2"
value = "Bottum-array1-2"
},
#{
linenumber = "3"
value = "Bottum-array1-3"
},
#{
linenumber = "4"
value = "Bottum-array1-4"
}
)
array2 = #(
#{
linenumber = "1"
value = "Bottum-array2-1"
},
#{
linenumber = "2"
value = "Bottum-array2-2"
}
)
}
MidHashtable2 = #{
array3 = #(
#{
linenumber = "1"
value = "Bottum-array3-1"
},
#{
linenumber = "2"
value = "Bottum-array3-2"
},
#{
linenumber = "3"
value = "Bottum-array3-3"
},
#{
linenumber = "4"
value = "Bottum-array3-4"
}
)
array4 = #(
#{
linenumber = "1"
value = "Bottum-array4-1"
},
#{
linenumber = "2"
value = "Bottum-array4-2"
}
)
}
MidHashtable3 = #{
array5 = #(
#{
linenumber = "1"
value = "Bottum-array5-1"
},
#{
linenumber = "2"
value = "Bottum-array5-2"
},
#{
linenumber = "3"
value = "Bottum-array5-3"
}
)
}
}
}
$newobject = #(
#{
linenumber = "1"
value = "newobject-1"
},
#{
linenumber = "2"
value = "newobject-2"
}
)
$query = "tophashtable.MidHashtable1.array2"
# This does work to read the content of the subobject true a variable
Invoke-Expression "`$object.$query"
# This does work to set the content of the subobject to the newobject
$object.tophashtable.MidHashtable1.array2 = $newobject
# I would like to set the content of the subobject by the value of the query variable.
# The value of the query variable can be of different length as wel.
# Sometime's i want to change the array's but sometime's i would like to change the content of the midhashtable's completely so $query would only be "tophashtable.MidHashtable1"
This does work, i Know the Invoke-Expression's are pure evil, but for now it's the only solution i can find.
Be verry carefull using this !!!
Invoke-expression "`$Object.$query = `$newobject”

How to convert a nested hashtable to PSObjects in PowerShell

If you have a hashtable containing nested hashtables, how to convert that to a PsObject recursively?
#{
Foo = #{
Bar = #{
Key = 'Value'
Test = 1
}
}
}
The result should be
$_.Foo.Bar.Key = 'Value'
$_.Foo.Bar.Test = 1
One approach is to create a recursive function:
function ConvertTo-PsObject {
param (
[hashtable] $Value
)
foreach ( $key in $Value.Keys | Where-Object { $Value[$_].GetType() -eq #{}.GetType() } ) {
$Value[$key] = ConvertTo-PsObject $Value[$key]
}
New-Object PSObject -Property $Value | Write-Output
}
Another way of doing it which hasn't been mentioned so far, and is more appropriate in many cases is to use New-Object PSObject.
$a = #{
Foo = #{
Bar = #{
Key = 'Value'
Test = 1
}
}
}
$b = New-Object -Type PSObject -Property $a
Doing it this way makes it work correctly with Format-Table for instance, and probably other places too.
Cast it to a [PsCustomObject]:
$a = [PsCustomObject]#{
Foo = #{
Bar = #{
Key = 'Value'
Test = 1
}
}
}
$a.Foo.Bar.Key # --> "Value"
$a.Foo.Bar.Test # --> 1

Find duplicates in array of hashtables

I have an array of hashtables and I need to find if there are elements who has the same Name.
I have this HasDuplicate function which return True or False if the array contains duplicate or not.
What I am doing here is that I am iterating through each element and add Name of it to another array, and then check it if it exists. But this code does not looks good, and I was thinking if there is another way of achieving this
# object looks like this
$array = #(
#{ Name = 'First', Passed = $True }
#{ Name = 'First', Passed = $False }
)
Function HasDuplicate
{
param($array)
$all = #()
foreach($item in $array)
{
$item_name = $item.Name
if($all -contains $item_name)
{
Write-Error "Duplicate name ""$item_name"""
return $True
}
else
{
$all += $item_name
}
}
return $False
}
Group-Object is probably the easiet, something like this:
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
)
$array.Name | Group-Object | Where-Object Count -GT 1
Another way you could do it using an hash table:
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
)
$h = #{}
$array | % {$h[$_.Name] += 1 }
$h.GetEnumerator() | Where value -GT 1
This might not be very good looking compared to the other answers, but you could just count your names in another hashtable then output the duplicates afterwards.
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
);
# Count names in array
$counts = #{}
foreach ($object in $array) {
$name = $object.Name
if (-not $counts.ContainsKey($name)) {
$counts[$name] = 0
}
$counts[$name] += 1
}
# Output duplicates
foreach ($name in $counts.Keys) {
if ($counts[$name] -gt 1) {
Write-Output ("Duplicate Name: " + $name)
}
}
Output:
Duplicate Name: First

PowerShell splatting merging to one hashtable

This code does what I want it to do:
$EventParams = #{
LogName = $LogName
Source = $Source
}
$EventInfoParams = $EventParams + #{
EntryType = 'Information'
EventID = '0'
}
$EventWarnParams = $EventParams + #{
EntryType = 'Warning'
EventID = '1'
}
$EventErrorParams = $EventParams + #{
EntryType = 'Error'
EventID = '2'
}
On this blog I found out there are maybe nicer/cleaner ways of writing this in one big hashtable. So I tried the following:
$EventParams = #{
LogName = $LogName
Source = $Source
Info = #{
EntryType = 'Information'
EventID = '0'
}
Warn = #{
EntryType = 'Warning'
EventID = '1'
}
Error = #{
EntryType = 'Error'
EventID = '2'
}
}
$EventParams.Info
This works fine to except that I can't get the variables from in the first example $EventParams in each of the single hashtables without duplicating data. Is there a way to have it all in one big hasthable?
Typically, you'd write all or most of the events from a given script to a common log and source. If you're wanting to avoid code duplication you can set this once for all the events that will be written from the script using $PSDefaultParameters at the beginning of the script:
#Set event loggin defaults
$PSDefaultParameterValues =
$PSDefaultParameterValues.Clone() + #{
'Write-Eventlog:LogName' = $LogName
'Write-Eventlog:Source' = $Source
}
Cloning it will create a new copy in the script, inheriting whatever defaults are already set in the parent or global scope without altering the hash table in that scope. The new $PSDefaultParameterValues will be disposed when the script finishes and the settings will revert back to whatever there are in the parent scope.
If you need to write to some other log or source somewhere in the script you can do that by specifying the LogName and Source for that event, overriding the defaults.
As far as I understand your question, you want to reference LogName and Source within the same hashtable? I doubt that this is possible.
However, You could go with a function:
function Get-EventParams
{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)]
[string]$LogName,
[Parameter(Mandatory=$true,Position=1)]
[string]$Source,
[Parameter(Mandatory=$true,Position=2, ParameterSetName='info')]
[switch]$Info,
[Parameter(Mandatory=$true,Position=2, ParameterSetName='warn')]
[switch]$Warn,
[Parameter(Mandatory=$true,Position=2, ParameterSetName='error')]
[switch]$Error
)
#{
LogName = $LogName
Source = $Source
}
if ($Info)
{
#{
EntryType = 'Information'
EventID = '0'
}
}
if ($Warn)
{
#{
EntryType = 'Warning'
EventID = '1'
}
}
if ($Error)
{
#{
EntryType = 'Error'
EventID = '2'
}
}
}
Now you can get the desired hashtable using for example:
Get-EventParams -LogName "Azure" -Source "Application" -Info
For convenience, you could also define a ValidateSet for your LogName and Soruce parameter.