Dynamically pass parameters to DSC resource? - powershell

I was working on a composite resource when I came across the issue of being able to dynamically pass parameters to a DSC resource and wondered if there's another way to tackle this that I'm missing.
Basically, I have the an array (Below) that contains one PSObject per desired file-share. Each of these PSObjects has properties that are used as parameters for my DSC resource (In this case the cLocalFileShare community resource).
The issue is, not all of these objects have all of the parameters defined. For example, some of my shares don't have any users/groups assigned to the ReadAccess permission, but in my ForEach loop (Below), a $null value is being passed to the actual resource as this permissions isn't defined, and this causes the resource to error as it is trying to set ReadAccess permissions to user $null.
My issue is, how do I tackle this - for this resource and others?
I've tried splatting the parameters in the DSC resource, but this doesn't seem to be supported. If this worked, I could build a different parameter list and pass that.
Somebody on Reddit suggested passing a string that contained all of the parameters, but again this doesn't seem to be supported.
My worst fear is, I will have to edit each resource to support (and ultimately ignore) $null values which seems like a really bad way to tackle this.
So, here's my array containing a PSObject per file share.
$MyConfig = #(
#{
Path = 'D:\Shares\Accounting'
Name = 'Accounting'
Ensure = 'Present'
ChangeAccess = 'AccountingAdmins'
ReadAccess = 'AccountingInterns,FinanceDepartment'
}
#{
Path = 'D:\Shares\Software'
Name = 'Software$'
Ensure = 'Present'
ReadAccess = 'DomainUsers'
}
)
Now, within the actual DSC configuration...
configuration {
ForEach ($ShareProperties in $MyConfig) {
# Each resource is named after the Path specified, but with the colon replaced as that's not valid character for the resource name
cLocalFileShare $ShareProperties.Path.Replace(':','__') {
Path = $ShareProperties.Path
Name = $ShareProperties.Name
Ensure = $ShareProperties.Ensure
ChangeAccess = $ShareProperties.ChangeAccess
ReadAccess = $ShareProperties.ReadAccess
}
}
}

Not the most elegant solution. But you could use a conditional section within your ForEach loop.
configuration {
ForEach ($ShareProperties in $MyConfig) {
# Each resource is named after the Path specified, but with the colon replaced as that's not valid character for the resource name
if ($ShareProperties.ChangeAccess -eq $null) {
cLocalFileShare $ShareProperties.Path.Replace(':','__') {
Path = $ShareProperties.Path
Name = $ShareProperties.Name
Ensure = $ShareProperties.Ensure
ReadAccess = $ShareProperties.ReadAccess
}
}
else
{
cLocalFileShare $ShareProperties.Path.Replace(':','__') {
Path = $ShareProperties.Path
Name = $ShareProperties.Name
Ensure = $ShareProperties.Ensure
ChangeAccess = $ShareProperties.ChangeAccess
ReadAccess = $ShareProperties.ReadAccess
}
}
}
}

Related

PowerShell - attempting to build a variable based on concatenated string + PSObject member values

Good day. I am working on an AD computer object creation script that has evolved. I have looked for some ways to dynamically pull data out of a storage object; this started with a hashtable, but then I realized I need multiple "columns" of values so that I could request a 2nd or 3rd value that is stored.
This led me to the creation of some PSObjects. I have a simple array as well.
$SiteCode_SHA = [PSCustomObject]#{
CountryCode = "CH"
SiteCode = "SHA"
ContainerCode = "CHA"
}
$SiteCode_IND = [PSCustomObject]#{
CountryCode = "IN"
SiteCode = "MOB"
ContainerCode = "IND"
}
[array]$ComputerTypeArray = "LT", "PC", "PR"
[string]$SiteCodeInput = (Read-Host -Prompt "Prompt #1 Input Options: SHA, IND")
$ProposedComputerName = '$SiteCode_' + $SiteCodeInput + '.CountryCode'
I am attempting to build a variable that contains the CountryCode from one of the $SideCode_XXX objects, plus the SiteCode from the same object, plus a value from the $ComputerTypeArray (which I am successfully retrieving through Read-Host.)
I have a three character variable that is being input through Read-Host that matches the suffix of the $SiteCode_XXX object. I cannot figure out how to dynamically use the object and its contained values after obtaining input from the user.
Concatenation of texts does not allow me to utilize the PSObject or its contained values, which I expected to be the failure point, but I cannot figure out the standard for getting around this. The goal is to set the variable $ProposedComputerName to "IN" if the $SiteCodeInput is "IND", or "CH" if the $SiteCodeInput is "SHA". (There are other components to the computer name that I am dealing with once this single problem is resolved.)
Thank you for your time.
Instead of using multiple variables, organize your data into a hashtable that use the site code as its key for each entry:
# define the data objects, store them in a simple array
$Sites = #(
[PSCustomObject]#{
CountryCode = "CH"
SiteCode = "SHA"
ContainerCode = "CHA"
}
[PSCustomObject]#{
CountryCode = "IN"
SiteCode = "MOB"
ContainerCode = "IND"
}
)
# now create a hashtable that maps each site code to the corresponding object
$SiteCodeMap = #{}
$Sites |ForEach-Object {
$SiteCodeMap[$_.SiteCode] = $_
}
# ... and now we're ready to take user input
$SiteCodeInput = (Read-Host -Prompt "Prompt #1 Input Options: SHA, IND")
# remember to test whether the input is valid
if($SiteCodeMap.ContainsKey($SiteCodeInput)){
# use valid site code to resolve the corresponding data object and grab the CountryCode value
$ProposedComputerName = $SiteCodeMap[$SiteCodeInput].CountryCode
} else {
Write-Warning "No site with site code '$SideCodeInput' exists!"
}

ExtendedPropertyDefinition declaration for EmailMessage in Powershell throws exception

since I got to know that ExtendedProperties have its limit for a specific mailbox in the EWS cloud I am trying to switch up my code to have only one ExtendedProperty and just change its value each time I am assigning the property to an e-mail message I am sending to then find it and work on the e-mail message object later on in the program.
I am having a hard time setting this up correctly even though I am following the docs, but it just seems to not work out for me.
This is the code part that throws an Exception: "Multiple ambigious overloads found for "ExtendedPropertyDefinition" and the argument count "3" :
# email declaration exposing the $email object
.
.
.
# property declaration and setting the value
# since I want to have only one extended property, this is actually a valid GUID string that I then # convert to a Guid type
$GUIDproperty = "00000000-0000-0000-0000-000000000000"
$propertyGUID = [Guid]$GUIDproperty
# since I want to have a unique value each time set to the existing extended property
$propertyValue = [guid]::NewGuid().ToString()
$propertyName = "Id"
$ExtendedProperty = [Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition]::new($propertyGUID, $propertyName, $propertyType)
# well I dont even reach this part, but just for the big picture
$email.SetExtendedProperty($ExtendedProperty, $propertyValue)
The docs I have followed for that are the following:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.extendedpropertydefinition.-ctor?view=exchange-ews-api#microsoft-exchange-webservices-data-extendedpropertydefinition-ctor(microsoft-exchange-webservices-data-defaultextendedpropertyset-system-string-microsoft-exchange-webservices-data-mapipropertytype)
https://learn.microsoft.com/en-us/dotnet/api/microsoft.exchange.webservices.data.folder.setextendedproperty?redirectedfrom=MSDN&view=exchange-ews-api#Microsoft_Exchange_WebServices_Data_Folder_SetExtendedProperty_Microsoft_Exchange_WebServices_Data_ExtendedPropertyDefinition_System_Object_
https://learn.microsoft.com/en-us/dotnet/api/system.guid?view=net-7.0
The following works okay for me
$propertyType = [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String
$GUIDproperty = "82e3d64f-e26d-4321-8fc3-c31aa790197c"
$propertyGUID = [Guid]$GUIDproperty
$propertyValue = [guid]::NewGuid().ToString()
$propertyName = "MyPropId"
$ExtendedProperty = [Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition]::new($propertyGUID, $propertyName, $propertyType)
return $ExtendedProperty
You don't specify what you using in the $propertyType so that maybe it, could also be to do with the versions you using. What version of PowerShell and the EWS Managed API are you trying ?

How to replace/update the Value of an Attribute in LDAP Directory using PowerShell?

The entries in our companys Non-AD LDAP Server look like this:
uid = e145871
sn = Smith
givenName = John
department = Research & Development
department = Human Resource
And so on...
I've developed a PowerShell script to add specific attributes and values which is working just fine. Now I need to replace specific values but the issue is the identical attribute name. (In this case it's "department")
My goal is to replace "Research & Development" with "Something Else". If I run the following script it gets replaced but Human Resource is deleted as well. Is it possible to replace only one value without touching/deleting the other?
$r = New-Object -TypeName System.DirectoryServices.Protocols.ModifyRequest
$r.DistinguishedName = "uid=e145871,ou=identities,ou=users,o=items,dc=company,dc=domain,dc=com"
$DirectoryRequest_value = New-Object "System.DirectoryServices.Protocols.DirectoryAttributeModification"
$DirectoryRequest_value.Name = "department"
$DirectoryRequest_value.Contains("Research & Development")
$DirectoryRequest_value.Operation = [System.DirectoryServices.Protocols.DirectoryAttributeOperation]::Replace
$DirectoryRequest_value.Add("SomethingElse")
$r.Modifications.Add($DirectoryRequest_value)
$result = $connection.SendRequest($r)
Thanks!
The LDAP Replace operation replaces (or overwrites) the entire value of the attribute, including any existing values that might exist as part of a multi-valued attribute.
From RFC4511 ยง4.6 - "Modify Operation":
- operation: Used to specify the type of modification being
performed. Each operation type acts on the following
modification. The values of this field have the following
semantics, respectively:
[...]
replace: replace all existing values of the modification
attribute with the new values listed, creating the attribute
if it did not already exist. A replace with no value will
delete the entire attribute if it exists, and it is ignored
if the attribute does not exist.
Instead, add two separate modifications to the request - one to add "SomethingElse" and one to remove "Research & Development":
$targetObject = 'uid=e145871,ou=identities,ou=users,o=items,dc=company,dc=domain,dc=com'
$attributeName = 'department'
$oldValue = 'Research & Development'
$newValue = 'SomethingElse'
$request = [System.DirectoryServices.Protocols.ModifyRequest]::new()
$request.DistinguishedName = $targetObject
# This modification will add the new value "SomethingElse"
$addNewDepartment = #{
Name = $attributeName
Operation = 'Add'
} -as [System.DirectoryServices.Protocols.DirectoryAttributeModification]
$addNewDepartment.Add($newValue) |Out-Null
$request.Modifications.Add($addNewDepartment) |Out-Null
# This modification will remove the old value "Research & Development"
$removeOldDepartment = #{
Name = $attributeName
Operation = 'Delete'
} -as [System.DirectoryServices.Protocols.DirectoryAttributeModification]
$removeOldDepartment.Add($oldValue) |Out-Null
$request.Modifications.Add($removeOldDepartment) |Out-Null
$result = $connection.SendRequest($request)

Why is PowerShell DSC saying I have a duplicate resource identifier when they are in different nodes?

I'm trying to do something relatively simple in PowerShell DSC. I want to ensure that the same file is on two servers:
configuration.ps1:
Configuration MyConfig {
# I want this block to be common to both nodes
Node $AllNodes {
File DirectoryCopy {
Ensure = "Present"
Type = "File"
Recurse = $true
SourcePath = ".\example.txt"
DestinationPath = "%userprofile%\example.txt"
PsDscRunAsCredential = Get-Credential
}
}
}
Agents -ConfigurationData .\data.psd1
data.psd1:
#{
AllNodes = #(
#{
NodeName = "server1"
Role = "ExampleRole1NotUsedYet"
},
#{
NodeName = "server2"
Role = "ExampleRole2NotUsedYet"
}
)
}
This doesn't work, and produces errors:
PSDesiredStateConfiguration\File : A duplicate resource identifier
'[File]DirectoryCopy' was found while processing the specification for
node 'System.Collections.Hashtable'. Change the name of this resource
so that it is unique within the node specification.
I think there's some basic concept about PowerShell DSC that I'm missing out on. Is there a way for me to apply this file to both nodes? Ideally, I'd like to apply some resources globally, and then apply some to just dev/prod systems.
$AllNodes is an array containing [hashtable]s, so when you use it directly, it's being enumerated, and then each element (a [hashtable]) when referenced as a node name is being converted to a string, which is to just going to display the class name; that's why the error says your node is called System.Collections.Hashtable instead of a name you expect.
Since both hashtables will end up being the same string (regardless of their contents), you are trying to create 2 File resources with the same name for the same node, which won't work.
What you want is to reference the elements of each hashtable, in this case the NodeName:
Configuration MyConfig {
# I want this block to be common to both nodes
Node $AllNodes.NodeName {
File DirectoryCopy {
Ensure = "Present"
Type = "File"
Recurse = $true
SourcePath = ".\example.txt"
DestinationPath = "%userprofile%\example.txt"
PsDscRunAsCredential = Get-Credential
}
}
}

Powershell, retrieve users manager

I'm creating a csv type org chart and was just wondering what would be the preferred to retrieve a users manager, manager's manager, ... etc up to the highest position. Currently i'm using:
[string]$man = $userEntry.manager
[array]$manName = $man.split('=,')
$manager = $manName[1]
$item.Cells.Item($i,1) = $userEntry.name.value
$item.Cells.Item($i,2) = $userEntry.description.value
$item.Cells.Item($i,3) = $manager.ToString()
then running get-QADobject to find the next manager by their DN.
but there must be a much cleaner way!
Thanks
If I'm understanding you correctly, you want to follow the chain of command all the way up to the very top (where presumably that person has no manager?). In that case, you need to recursively walk up the tree.
Untested pseudocode as I don't have a domain handy at the moment to test with:
Function Get-Manager {
params(
[string]$username
)
$userEntry = get-qaduser $username
[string]$man = $userEntry.manager
if (-not ($man -eq $null)) {
[array]$manName = $man.split('=,')
$manager = $manName[1]
"$manager is the manager of $username";
Get-Manager $manager
}
}
This will come to a halt once a user has no manager. In my organization's case, our CEO is listed as his own manager, so I'd change the above code to look for the manager to be non-null or equal to the user, so that either of those conditions being true broke the loop.