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
Related
I'm working on a project for work and I need some help. I'm able to pass one argument, multiple arguments, and all arguments, when the arguments are in order(CapA,CapB,etc.) but my function is useless whenever I try to pass the arguments out of order. Ex.(CapB,CapA) I know how to do it in C++, but I'm not sure how to do it in PowerShell. Please see the following code:
function Get-FieldOps
{
<#
.SYNOPSIS
Generates list of technicians for each Field Service Team.
.Description
Generates list of technicians for each Field Service Team.
It is able to generate a list of technicians for one team name, multiple team names, or all team names.
.Parameter Team
Team has to be a string variable or list of string variables.
If value passed to team doesn't match a Field Service Team then it will error out.
.Example
Get-FieldOps -Team CapF
.Example
Get-FieldOps CapF
.NOTES
Created on: 12/22/2021
Created by:
FileName:
#>
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true)]
[String[]]$Team
)
$OTS_EUC_Field_Ops = #('CapA','CapB', 'CapC','CapD', 'CapE', 'CapF', 'Delivery', 'Network', 'NorthA', 'NorthB', 'NorthC', 'Projects', 'SouthA', 'SouthB', 'SouthC', 'SouthD')
$OTS_Ivanti_Groups = #('_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2A','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2B','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2C','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2D','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2E', '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2F','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-Delivery','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-Network', '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-North-A','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-North-B','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-North-C','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-Projects-1361711097','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-A','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-B','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-C','_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-D')
for($index=0;$index -lt $OTS_EUC_Field_Ops.Length;$index++)
{
if($Team[$index] -like $OTS_EUC_Field_Ops[$index])
{
Write-Host "************************************************"
$OTS_EUC_Field_Ops[$index]
Get-ADGroupMember $OTS_Ivanti_Groups[$index] |Format-Table -Property name,SamAccountName,distinguishedName -AutoSize
}
if($Team -like "ALL")
{
Write-Host "************************************************"
$OTS_EUC_Field_Ops[$index]
Get-ADGroupMember $OTS_Ivanti_Groups[$index] |Format-Table -Property name,SamAccountName,distinguishedName -AutoSize
}
}
}
Use a hashtable (an unordered associative array) instead of 3 arrays - this way you don't need to worry about alignment between the user input and the existing mapping between team names and group names:
# Define mapping
$FieldTeamGroupMapping = #{
'CapA' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2A'
'CapB' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2B'
'CapC' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2C'
'CapD' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2D'
'CapE' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2E'
'CapF' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-2F'
'Delivery' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-Delivery'
'Network' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-Network'
'NorthA' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-North-A'
'NorthB' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-North-B'
'NorthC' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-North-C'
'Projects' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-Projects-1361711097'
'SouthA' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-A'
'SouthB' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-B'
'SouthC' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-C'
'SouthD' = '_DOA-OTS-Ivanti-Team-OTS-EUC-Field-South-D'
}
if($Team -contains 'ALL'){
# If the user specified `ALL`, resolve all team names
$Team = $FieldTeamGroupMapping.Keys
}
# Loop over list of team names and do operations
foreach($teamName in $Team){
if(-not $FieldTeamGroupMapping.ContainsKey($teamName)){
Write-Error "Team name '$teamName' is not recognized"
}
else {
Write-Host "************************************************"
$teamName
Get-ADGroupMember $FieldTeamGroupMapping[$teamName] |Format-Table -Property name,SamAccountName,distinguishedName -AutoSize
}
}
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]
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"
})
I am attempting to pass a list of objects from one function to another, one by one.
First function: generate a list of users (objects) near expiry;
Second function: send an email to each user (object)
The first function works fine and outputs a group of objects (or so it would seem) and the second function will accept input and email a single user without issue.
Issues arise only when multiple objects are passed from the first function to the second.
Relevant code snippets are below:
The First function creates a custom object for each located user and adds it to an array, which is then outputted in the end block. Below is an extremely simplified snippet of the code with the essential object creation step:
Function 01
{
#param block goes here etc...
Foreach ($user in $users)
{
$userOutput = #()
$userTable = New-Object PSObject -Property #{
name = $User.Name
SamAccountName = $User.SamAccountName
emailAddress = $User.EmailAddress
expired = $user.PasswordExpired
expiryDate = $ExpiryDate.ToShortDateString()
daysTillExpiry = $daysTillExpiry
smtpRecipientAddress = $User.EmailAddress
smtpRecipientName = $User.Name
}
$userOutput += $userTable
}
Write-Output $userOutput
}
I have also tried writing each custom object ($userTable) straight to the console within each iteration of the Foreach (users) loop.
The Second function accepts pipeline input for a number of matching parameters from the first function, e.g:
[Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string]$smtpRecipientName
The second function also calls a third function designed specifically to send smtp mail and contains no loops, it just takes the current object from the pipeline and deals with it.
I haven't included the full code for either mail function because it is largely irrelevant. I just want to know whether the objects outputted from the first function can be dealt with one-by-one by the second.
At present, the mail function deals with the first object passed to it, and no others.
Update:
This is what I have in mind (but the second function only deals with the last object that was piped in:
Function Test-UserExp
{
$iteration = 0
For ($i=0;$i -le 9;$i++)
{
$iteration ++
$userTable = New-Object PSObject -Property #{
expiryDate = "TestExpDate_$iteration"
daysTillExpiry = "TestDaysTillExpiry_$iteration"
smtpRecipientAddress = "TestSMTPRecipientAddress_$iteration"
smtpRecipientName = "TestSMTPRecipientName_$iteration"
}
$userTable
}
}
Function Test-MailSend
{
Param
(
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$expiryDate,
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$daysTillExpiry,
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$smtpRecipientAddress,
[Parameter(ValueFromPipelineByPropertyName=$true)][string]$smtpRecipientName
)
Write-Host 'Output from Test-MailSend:'
$expiryDate
$daysTillExpiry
$smtpRecipientAddress
$smtpRecipientName
}
First of all: if you want to process objects in a pipeline, one at the time, do not kill experience by collecting all the objects - that's only necessary if you intend to do something about whole collection at some point. If not than just output objects as soon as you get them:
foreach ($user in $users) {
New-Object PSObject -Property #{
name = $User.Name
SamAccountName = $User.SamAccountName
emailAddress = $User.EmailAddress
# ...
}
}
In your case you output whole collection at the end. That's hardly a pipeline experience if you would ask me.
For the second command: if you intend to create parameter for each property, just leave the part 'ValueFromPipeline' out. Otherwise you may end up with whole object converted to string... If you want to take an object as a whole, leave out 'ValueFromPipelineByPropertyName' and specify correct type. And make sure you have process {} wrapped around the code that uses parameters taken from pipeline.
And finally: why would you write a function to send mails? You have Send-MailMessage, so unless you do something this cmdlet doesn't cover, you probably don't need hand-crafted replacement...
In function 1 you want to create the array before the ForEach loop, so you aren't re-creating the array every iteration.
In the param block for the second function, you want to declare the parameter as an array of strings, not just a string.
Finally, when accepting pipeline input for the second function you will need to use the Begin, Process, and End blocks. The part of the function that repeats for each item should be in the Process block.
Here is a short working sample below:
Function fun1{
$users = #(1,2,3)
$userOutput = #()
Foreach ($user in $users){
$userTable = New-Object PSObject -Property #{
emailAddress = "$user#blah.com"
}
$userOutput += $userTable
}
$userOutput
}
Function fun2{
param(
[parameter(ValueFromPipeLine=$true)]
[String[]]$Recipients
)
begin{}
process{
ForEach ($Recipient in $Recipients){
$_
}
}
end{}
}
fun1 | Select emailAddress | fun2
This will give you the output below:
emailAddress
------------
1#blah.com
2#blah.com
3#blah.com
Here is a great breakdown of how the Begin/Process/End blocks work in PowerShell http://technet.microsoft.com/en-us/magazine/hh413265.aspx
function Set-UserExpiry {
1..10 | foreach {
[PSCustomObject]#{
ExpiryDate = "TestExpDate_$_"
DaysTillExpiry = "TestDaysTillExpiry_$_"
SmtpRecipientAddress = "TestSMTPRecipientAddress_$_"
SmtpRecipientName = "TestSMTPRecipientName_$_"
}
}
}
function Test-UserExpiry {
param
(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$ExpiryDate,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$DaysTillExpiry,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$SmtpRecipientAddress,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[string]$SmtpRecipientName
)
process {
Write-Output 'Output from Test-MailSend:'
$expiryDate
$daysTillExpiry
$smtpRecipientAddress
$smtpRecipientName
Write-Output ''
}
}
Set-UserExpiry | Test-UserExpiry
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.