Powershell Printing Custom per Printer Permissions - powershell

First off I would like to thank everyone for helping me work thru my issue.
Scope:
I am looking to write a script that will dynamically build the full set of permissions for each printer. As each printer has it's own Dynamic Group and is not allowed to have the everyone group applied to the printer.
Example:
Printer Name: PrinterA
AdGroup for Printer: gprt_PrinterA
Other groups assigned full (Print/Manage Doc/Manage Printer) permissions to the printer : Local Admin/Local Power User/Local Print Operator/Network Admins (Domain Group)
Other groups with Manage Documents and Print permissions to the printer: Endpoint (Domain Group)/Service Desk (Domain Group)/gprt_PrinterA (Domain Group)\
First what works and I see many examples about this across the web but does not meet my requirements:
$DefaultPrinterInfo = Get-Printer -Name PrinterA -Full
Set-Printer -Name PrinterB -PermissionSDDL ($DefaultPrinterInfo.PermissionSDDL)
IMPORTANT:
This however does not work to meet the required specifications. The reason is the gprt_PrinterA group can not exist on PrinterB. PrinterB must have the gprt_PrinterB Group.
In one example I have attempted to:
Set-Printer -Name PrinterB -PermissionSDDL "G:SYD:(A;;LCSWSDRCWDWO;;;BA)(A;OIIO;RPWPSDRCWDWO;;;BA)"
I have attempted to even dynamically create the default permission groups required and if this worked then it would be easy for me to just add 1 more group that is dynamically assigned:
(A;;LCSWSDRCWDWO;;;BA)(A;OIIO;RPWPSDRCWDWO;;;BA)
(A;;LCSWSDRCWDWO;;;PU)(A;OIIO;RPWPSDRCWDWO;;;PU)
(A;;LCSWSDRCWDWO;;;PO)(A;OIIO;RPWPSDRCWDWO;;;PO)
(A;;LCSWSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69794)(A;OIIO;RPWPSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69794)
(A;CIIO;RC;;;S-1-5-21-51083937-621610274-1850952788-69792)(A;OIIO;RPWPSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69792)(A;;SWRC;;;S-1-5-21-51083937-621610274-1850952788-69792)
(A;CIIO;RC;;;S-1-5-21-51083937-621610274-1850952788-69791)(A;OIIO;RPWPSDRCWDWO;;;S-1-5-21-51083937-621610274-1850952788-69791)(A;;SWRC;;;S-1-5-21-51083937-621610274-1850952788-69791)
I kept the groups clean for easy reading but essentially just make it a continuous line with "G:SYD:" in the beginning. Then replace the PermissionSDDL in the above powershell statement. Either way though, I keep getting the error: "[Set-Printer : Access was denied to the specific resource]"
I have even attempted to do the following:
SetSecurityDescriptor method of the Win32_Printer class
Set-PrinterPermission.ps1
The Security Descriptor Definition Language of Love (Part 2)
Adding Multiple Permissions to a Share
These did put me on the correct path! It lets me replace the permission on the printer. But it strips all existing permission, putting on only the single permission specified for the printer. I need to apply a whole set of permissions to the printer as you see above. I am a little out of my realm but learning how to build a Multi-ACL Package to apply to the printer.
I am ok with replacing all permissions, if I can assign a whole set of permissions, or simply add and remove to the existing permissions if they do or not exist.
What I have learned in my research the permission sets need to be:
Print/Manage this Printer
# G:SYD:(A;;LCSWSDRCWDWO;;;$SID)
Print
# G:SYD:(A;;SWRC;;;$SID)
Print/Manage this Printer/Manage Documents/Special Permissions
# G:SYD:(A;;LCSWSDRCWDWO;;;$SID)(A;OIIO;RPWPSDRCWDWO;;;$SID)
I hope someone the help me figure out a solution please.
Ok so after extensively researching I am getting closer.
The "Set-PrinterPermission" script is on the correct path. What I have had to do, is stripped out the ACE function from the script to place it into it's own function.
function New-PrinterACE
{
##[CmdletBinding(SupportsShouldProcess)]
Param (
[Parameter(
Mandatory = $true,
HelpMessage = "User/group to grant permissions"
)]
[String]$UserName,
[Parameter(
Mandatory = $true,
HelpMessage = "Permissions to apply"
)]
[ValidateSet('Takeownership', 'ReadPermissions', 'ChangePermissions', 'ManageDocuments', 'ManagePrinters', 'Print + ReadPermissions')]
[String]$Permission,
[Parameter(
Mandatory = $true,
HelpMessage = "Permissions to apply"
)]
[ValidateSet('Allow', 'Deny', 'System Audit')]
[String]$AccessType
)
$Ace = ([WMIClass] "Win32_Ace").CreateInstance()
$Trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
Write-Verbose "Translating UserName (user or group) to SID"
$SID = (New-Object security.principal.ntaccount $UserName).translate([security.principal.securityidentifier])
Write-Verbose "Get binary form from SID and byte Array"
[byte[]]$SIDArray = , 0 * $SID.BinaryLength
$SID.GetBinaryForm($SIDArray, 0)
Write-Verbose "Fill Trustee object parameters"
$Trustee.Name = $UserName
$Trustee.SID = $SIDArray
Write-Verbose "Translating $Permission to the corresponding Access Mask"
Write-Verbose "Based on https://learn.microsoft.com/en-US/windows/win32/cimwin32prov/setsecuritydescriptor-method-in-class-win32-printer?redirectedfrom=MSDN"
Write-Verbose "https://social.technet.microsoft.com/Forums/Windows/en-US/a67e3ffd-5e41-4e2f-b1b9-c7c2f29a3a12/adding-permissions-to-an-existing-share"
switch ($Permission)
{
'Takeownership'
{
$Ace.AccessMask = "524288"
}
'ReadPermissions'
{
$Ace.AccessMask = "131072"
}
'ChangePermissions'
{
$Ace.AccessMask = "262144"
}
'ManageDocuments'
{
$Ace.AccessMask = "983088"
}
'ManagePrinters'
{
$Ace.AccessMask = "983052"
}
'Print + ReadPermissions'
{
$Ace.AccessMask = "131080"
}
}
Write-Verbose "Translating $AccessType to the corresponding numeric value"
Write-Verbose "Based on https://learn.microsoft.com/en-US/windows/win32/cimwin32prov/setsecuritydescriptor-method-in-class-win32-printer?redirectedfrom=MSDN"
switch ($AccessType)
{
"Allow"
{
$Ace.AceType = 0
$Ace.AceFlags = 0
}
"Deny"
{
$Ace.AceType = 1
$Ace.AceFlags = 1
}
"System Audit"
{
$Ace.AceType = 2
$Ace.AceFlags = 2
}
}
Write-Verbose "Write Win32_Trustee object to Win32_Ace Trustee property"
$Ace.Trustee = $Trustee
Return $ACE
}
$MyPrinterAces = #()
$MyPrinterAces += New-PrinterACE -UserName <DomainUserA> -Permission ManagePrinters -AccessType Allow
$MyPrinterAces += New-PrinterACE -UserName <DomainUserA> -Permission ManageDocuments -AccessType Allow
$MyPrinterAces += New-PrinterACE -UserName "DomainGroupA" -Permission ManageDocuments -AccessType Allow
$MyPrinterAces += New-PrinterACE -UserName "DomainGroupA" -Permission 'Print + ReadPermissions' -AccessType Allow
#https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-security-descriptor-objects#example-checking-who-has-access-to-printers
#https://stackoverflow.com/questions/60261292/explicit-access-array-from-acl-win32-api
This, with a few other cosmetic modifications to the "Set-PrinterPermission" script to accommodate; So that it now references this function to build the ACE's it uses and to add the ability for it to accommodate an array of multiple users/groups with permissions types.
function Set-PrinterPermission
{
[CmdletBinding(SupportsShouldProcess)]
Param (
[Parameter(
Mandatory = $true,
HelpMessage = "Server or array of servers",
ParameterSetName = 'OnePrinter'
)]
[Parameter(
Mandatory = $true,
HelpMessage = "Server or array of servers",
ParameterSetName = 'AllPrinters'
)]
[string[]]$Servers,
[Parameter(
HelpMessage = "Name of the Printer",
ParameterSetName = 'OnePrinter'
)]
[String]$PrinterName,
$PrinterPermissions =
#(
#('Administrators', 'ManagePrinters','Allow'),
#('Power Users', 'ManagePrinters','Allow'),
#('Print Operators', 'ManagePrinters','Allow'),
#('OHD – Network Support Team', 'ManagePrinters','Allow'),
#("OHD – PC Support Team", 'Print + ReadPermissions','Allow'),
#("OHD - Service Desk Users", 'Print + ReadPermissions','Allow')
)
)
Begin
{
$greenCheck =
#{
Object = [Char]8730
ForegroundColor = 'Green'
NoNewLine = $true
}
ConvertFrom-SddlString -Sddl $printer.PermissionSDDL
#Write-Host "Status check... " -NoNewline
#Start-Sleep -Seconds 1
#Write-Host #greenCheck
#Write-Host " (Done)"
Write-Output "Beginning Treatment ..."
Write-Verbose "creating instances of necessary classes ..."
$SD = ([WMIClass] "Win32_SecurityDescriptor").CreateInstance()
$Aces = #()
Foreach ($PrinterPermission in $PrinterPermissions)
{
$Aces += New-PrinterACE -UserName $PrinterPermission[0] -Permission $PrinterPermission[1] -AccessType $PrinterPermission[2]
}
Write-Verbose "Write Win32_Ace and Win32_Trustee objects to SecurityDescriptor object"
$SD.DACL = $Aces
Write-Verbose "Set SE_DACL_PRESENT control flag"
$SD.ControlFlags = 0x0004
}
process
{
try
{
If ($PSCmdlet.ParameterSetName -eq "OnePrinter")
{
ForEach ($Server in $Servers)
{
$Printer = Get-Printer -ComputerName $Server -Name $PrinterName -ErrorAction Stop
$PrinterName = $Printer.name
Write-Output "Beginning treatment of: $PrinterName On: $Server"
Write-Verbose "Get printer object"
<#
It seems that i can't use the Filter parameter using a var
$PrinterWMI = Get-WMIObject -Class WIN32_Printer -Filter "name = $PrinterName"
I've also noticed that I've haven't the same result using Get-CimInstance in particular with
$PrinterCIM.psbase.scope
However I'm sure that using Get-CiMInstance will be better, but i don't know how to proceed
then I'm using the following "Legacy" approach
https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-security-descriptor-definition-language-of-love-part-1/ba-p/395202
https://techcommunity.microsoft.com/t5/ask-the-directory-services-team/the-security-descriptor-definition-language-of-love-part-2/ba-p/395258
http://docs.directechservices.com/index.php/category-blog-menu/319-the-security-descriptor-definition-language-of-love
https://learn.microsoft.com/en-us/windows/win32/secauthz/ace-strings?redirectedfrom=MSDN
https://learn.microsoft.com/en-us/windows/win32/secauthz/access-tokens
#>
#$PrinterWMI = (Get-WmiObject -Class WIN32_Printer | Where-Object -FilterScript { $_.Name -like "wilpa0p11" }).GetSecurityDescriptor().Descriptor.dacl
$PrinterWMI = Get-WmiObject -Class WIN32_Printer | Where-Object -FilterScript { $_.Name -like $PrinterName }
Write-Verbose "Enable SeSecurityPrivilege privilegies"
$PrinterWMI.psbase.Scope.Options.EnablePrivileges = $true
Write-Verbose "Invoke SetSecurityDescriptor method and write new ACE to specified"
$PrinterWMI.SetSecurityDescriptor($SD)
Write-Verbose "Treatment of $PrinterName : Completed"
}
} # end if OnePrinter Parameter Set
If ($PSCmdlet.ParameterSetName -eq "AllPrinters")
{
ForEach ($Server in $Servers)
{
$Printers = Get-Printer -ComputerName $Server | Where-Object { $_.Shared -eq $true } -ErrorAction Stop
ForEach ($Printer in $Printers)
{
$PrinterName = $Printer.name
Write-Output "Beginning treatment of : $PrinterName"
Write-Verbose "Get printer object"
<#
It seems that i can't use the Filter parameter using a var
$PrinterWMI = Get-WMIObject -Class WIN32_Printer -Filter "name = $PrinterName"
I've also noticed that I've haven't the same result using Get-CimInstance in particular with
$Printer.psbase.scope
then I'm using the following approach
However I'm sure that using Get-CiMInstance will be better
#>
$PrinterWMI = Get-WmiObject -Class WIN32_Printer | Where-Object -FilterScript { $_.Name -like $PrinterName }
Write-Verbose "Enable SeSecurityPrivilege privilegies"
$PrinterWMI.psbase.Scope.Options.EnablePrivileges = $true
Write-Verbose "Invoke SetSecurityDescriptor method and write new ACE to specified"
$PrinterWMI.SetSecurityDescriptor($SD)
Write-Output "Treatment of $PrinterName : Completed"
}
}
} # end if All Printers Parameter Set
} # End Try
catch
{
Write-Error "Hoops an error occured"
Write-Error $_.Exception.Message
}
}
end
{
Write-Output "All treatments : completed"
}
} # end function
Now this is working great I can easily add the dynamic group as a parameter and a ACE will get assigned to the security descriptor of the printer.
Now my problem is I am unable to add the "Manage Documents" permission to the printer. if anyone can help me with this I will have my project complete.
The permission is assigned correctly for Printing only, and Manage Printer.
Primary Issue needing help resolving:
I am so very close now... what am I doing wrong to apply the "Manage Documents" permission to the printer ACL?
The Image below is the results of the script trying to apply the "Manage Documents" Permissions.
Very Minor Cosmetic help:
is there a way to validate the $PrinterPermissions in the Parameters section of the code? My thinking is to validate the parameter in the begin section of the code and exit out if one of my validations fail. not sure if there is a better way.

Related

Can I set the "-Server" parameter for use with Active Directory cmdlets per PS session?

I'm administering three domains (let's call them xx.company.local, xy.company.local, and xz.company.local) and I've got domain admin accounts for all three domains.
If I run powershell as my xx domain admin user I don't need to specify the -Server parameter for AD cmdlets, but I do when running PS as the other two. I assume this is because my PC is only joined to the xx domain.
Is there a command that I can run when opening up the xy and xz powershell sessions that will set that -Server parameter? (I'm looking into $PSHome and $PSDefaultParameterValues, but figured I would ask before wasting too much time.)
Extra thought: I have three runas shortcuts for my ADUC gui (mmc.exe %SystemRoot%\system32\dsa.msc) and the one-time single-step for the xy and xz domains was to change the domain. I'm hoping that there's a similar step I can run for the powershell side of things.
So I was able to modify my script since nobody posted an answer.
first, I created a global variable in my .psm1 file
$domain_controller = Get-ADDomainController -Discover -Service PrimaryDC
then, I created a new function
function Set-DomainController {
[CmdletBinding()]
Param(
[ValidateSet(“xx”, ”xy”, ”xz”)]
[Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]
$xxxyxz
)
process {
if ($PSBoundParameters.ContainsKey('xxxyxz')) {
$script:domain_controller = Get-ADDomainController -Discover -Service PrimaryDC -Domain "$xxxyxz.company.local"
}
else {
Write-Output ""
Write-Output "You are currently using " $script:domain_controller.Hostname
Write-Output ""
Write-Output "Select a domain:"
Write-Output "[1] - xx.company.local"
Write-Output "[2] - xy.company.local"
Write-Output "[3] - xz.company.local"
Write-Output ""
$selection = Read-Host "Please make a selection"
switch ($selection) {
'1' {
$script:domain_controller = Get-ADDomainController -Discover -Service PrimaryDC -Domain xx.company.local
}
'2' {
$script:domain_controller = Get-ADDomainController -Discover -Service PrimaryDC -Domain xy.company.local
}
'3' {
$script:domain_controller = Get-ADDomainController -Discover -Service PrimaryDC -Domain xzcompany.local
}
}
Write-Output ""
Write-Output "You are currently using " $script:domain_controller.Hostname
Write-Output ""
}
}
}
Set-Alias -Name setdc -Value Set-DomainController
lastly I went through the rest of the functions in my module and added -Server $script:domain_controller to every Get-AD* call throughout
took a few hours, but now it's done
... still looking for something better ...

Add a computer to an AD group on a specific domain controller via ADSI adapter

I run this script in a user context that has privileges to add members to my AD group. I verified permissions already and I can add members manually via ADUC.
I'd like to add my machine to a specific group on a specific domain controller. I'm very unfamiliar with ADSI usage and I pieced together the below script based on other examples. I'm unable to use PS AD module at the time this script will be ran.
Param(
[Parameter(Mandatory)]
[string]$GroupName
)
#Find domain controllers
$searcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://OU=Domain Controllers,DC=corp,DC=thing,DC=com")
$searcher.Filter = "(objectclass=computer)"
$DomainControllers = $searcher.FindAll()
Write-Verbose "Found DCs:"
foreach ($dc in $DomainControllers.Properties.cn)
{
Write-Verbose "$dc"
}
$TargetController = $null
$ComputerDn = $null
foreach ($dc in $DomainControllers.Properties.cn)
{
$searcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://$dc/DC=corp,DC=thing,DC=com")
$searcher.Filter = "(&(objectclass=computer)(cn=$env:COMPUTERNAME))"
$result = $searcher.FindOne()
try {
if ($result)
{
$TargetController = $dc
Write-Verbose "Target controller set: $TargetController"
$ComputerDn = $result.Properties.distinguishedname
Write-Verbose "Computer DN: $ComputerDn"
break
}
else
{
Write-Verbose "Did not find $env:COMPUTERNAME on $dc"
}
}
catch
{
Write-Verbose "$dc ERROR"
}
}
if ($TargetController)
{
$GroupSearcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://$TargetController/DC=corp,DC=thing,DC=com")
$GroupSearcher.Filter = "(&(objectclass=group)(cn=$GroupName))"
$GroupDn = $GroupSearcher.FindOne().Properties.distinguishedname
$Group = [ADSI] "LDAP://$TargetController/$GroupDn"
$ComputerSearcher = New-Object System.DirectoryServices.DirectorySearcher([adsi] "LDAP://$TargetController/DC=corp,DC=thing,DC=com")
$ComputerSearcher.Filter = "(&(objectclass=computer)(cn=$env:COMPUTERNAME))"
$result = $ComputerSearcher.FindOne().Properties.memberof -match "cn=$GroupName,"
if (!$result)
{
try
{
$Computer = [adsi] "LDAP://$TargetController/$ComputerDn"
$Group.Add("$Computer")
}
catch
{
$_.Exception.Message ; Exit 1
}
}
else
{
Write-Verbose "$env:COMPUTERNAME already a member of $GroupName"
}
}
Running this I get the error "Exception calling "Add" with "1" argument(s): "Exception from HRESULT: 0x80005000". I'm open to any alternatives!
As Bill Stewart commented you should either remove the [adsi] accelerator
$Computer = "LDAP://$TargetController/$ComputerDn"
$Group.Add($Computer)
or specify the path
$Computer = [adsi] "LDAP://$TargetController/$ComputerDn"
$Group.Add($Computer.path)
The method expects the path which you've already constructed with "LDAP://$TargetController/$ComputerDn" which makes the cast to an adsi object unnecessary.

Get-ADPrincipalGroupMembership An unspecified error has occurred

I am getting errors with Get-ADPrincipalGroupMembership command on Windows 10 (x64) machine. I have installed the required RSAT- 'Active directory Domain service and Lightweight Directory service tools' and 'Server manager' dependencies as specified int this document. I am able to execute Get-AdUser and see the results but Get-ADPrincipalGroupMembership is throwing below error.
PS C:\Users\JYOTHI> Get-ADPrincipalGroupMembership jyothi
Get-ADPrincipalGroupMembership : An unspecified error has occurred
At line:1 char:1
+ Get-ADPrincipalGroupMembership gapalani
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (jyothi:ADPrincipal) [Get-ADPrincipalGroupMembership], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADPrincipalGroupMembership
I can try the other way
(Get-Aduser jyothi -Properties MemberOf | Select MemberOf).MemberOf
but like to know what is the fix for Get-ADPrincipalGroupMembership
As you have noticed, Get-ADPrincipalGroupMembership fails with an obscure error if the reference object's name contains certain characters, or if it's a member of one or more groups that contain certain characters in their names.
I don't have definitive proof, but my testing indicates that the underlying issue is that Get-ADPrincipalGroupMembership, internally, uses ADSI and fails to correctly escape distinguished names that contain characters that need to be escaped. (If this is the case, Microsoft should be using the IADsPathname interface to escape names correctly. This would be an embarrassing oversight on their part.)
Unfortunately, this problem renders the cmdlet broken and unusable in production environments.
Here's a relatively short PowerShell script that doesn't suffer from this annoyance and also supports retrieving recursive group memberships:
# Get-ADGroupMembership.ps1
# Written by Bill Stewart
#requires -version 2
# Version history:
# 1.0 (2019-12-02)
# * Initial version. Only searches the current domain.
<#
.SYNOPSIS
Gets the distinguished names of the Active Directory groups that have a specified object as a member.
.DESCRIPTION
Gets the distinguished names of the Active Directory groups that have a specified object, represented by the -Identity parameter, as a member.
.PARAMETER Identity
Specifies an Active Directory object. You can specify either the distinguishedName or the sAMAccountName of the object.
.PARAMETER Recursive
Specifies to include the object's nested group memberships.
.NOTES
If you use the ActiveDirectory PowerShell module and want Microsoft.ActiveDirectory.Management.ADGroup objects as output, pipe this command's output to the Get-ADGroup cmdlet.
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of:
PS C:\> Get-ADGroupMembership kendyer
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of, including nested groups:
PS C:\> Get-ADGroupMembership kendyer -Recursive
.EXAMPLE
Get the ADGroup objects representing the groups that the kendyer account is a member of (requires the Active Directory module):
PS C:\> Get-ADGroupMembership kendyer | Get-ADGroup
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,ValueFromPipeline = $true)]
[String[]] $Identity,
[Switch] $Recursive
)
begin {
$CommandName = $MyInvocation.MyCommand.Name
# Set up Pathname COM object
$ADS_ESCAPEDMODE_ON = 2
$ADS_SETTYPE_DN = 4
$ADS_FORMAT_X500_DN = 7
$Pathname = New-Object -ComObject "Pathname"
if ( -not $Pathname ) {
return
}
[Void] $Pathname.GetType().InvokeMember("EscapedMode","SetProperty",$null,$Pathname,$ADS_ESCAPEDMODE_ON)
# Outputs correctly escaped distinguished name using Pathname object
function Get-EscapedName {
param(
[String] $distinguishedName
)
[Void] $Pathname.GetType().InvokeMember("Set","InvokeMethod",$null,$Pathname,#($distinguishedName,$ADS_SETTYPE_DN))
$Pathname.GetType().InvokeMember("Retrieve","InvokeMethod",$null,$Pathname,$ADS_FORMAT_X500_DN)
}
# Outputs the memberOf attribute of an object using paged search (in case
# an object is a member of a large number of groups)
function Get-MemberOfAttribute {
param(
[String] $distinguishedName,
[Ref] $memberOf,
[Switch] $recursive
)
$searcher = [ADSISearcher] "(objectClass=*)"
$searcher.SearchRoot = [ADSI] "LDAP://$(Get-EscapedName $distinguishedName)"
$lastQuery = $false
$rangeStep = 1500
$rangeLow = 0
$rangeHigh = $rangeLow + ($rangeStep - 1)
do {
if ( -not $lastQuery ) {
$property = "memberOf;range={0}-{1}" -f $rangeLow,$rangeHigh
}
else {
$property = "memberOf;range={0}-*" -f $rangeLow
}
$searcher.PropertiesToLoad.Clear()
[Void] $searcher.PropertiesToLoad.Add($property)
$searchResults = $searcher.FindOne()
if ( $searchResults.Properties.Contains($property) ) {
foreach ( $searchResult in $searchResults.Properties[$property] ) {
if ( $memberOf.Value.Count -gt 100 ) {
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-CurrentOperation $searchResult
}
if ( $recursive ) {
if ( -not $memberOf.Value.Contains($searchResult) ) {
Get-MemberOfAttribute $searchResult $memberOf -recursive
}
}
if ( -not $memberOf.Value.Contains($searchResult) ) {
$memberOf.Value.Add($searchResult)
}
}
$done = $lastQuery
}
else {
if ( -not $lastQuery ) {
$lastQuery = $true
}
else {
$done = $true
}
}
if ( -not $lastQuery ) {
$rangeLow = $rangeHigh + 1
$rangeHigh = $rangeLow + ($rangeStep - 1)
}
}
until ( $done )
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-Completed:$true
}
function Get-ADGroupMembership {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[String] $identity,
[Switch] $recursive
)
$ldapString = $identity -replace '\\','\5c' -replace '\(','\28' -replace '\)','\29' -replace '\*','\2a' -replace '\/','\2f'
$searcher = [ADSISearcher] "(|(distinguishedName=$ldapString)(sAMAccountName=$ldapString))"
try {
$searchResults = $searcher.FindAll()
if ( $searchResults.Count -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$memberOf = New-Object Collections.Generic.List[String]
Get-MemberOfAttribute $searchResult.Properties["distinguishedname"][0] ([Ref] $memberOf) -recursive:$recursive
$memberOf
}
}
else {
Write-Error "Cannot find an object with identity '$identity'." -Category ObjectNotFound
}
}
catch {
Write-Error -ErrorRecord $_
}
finally {
$searchResults.Dispose()
}
}
}
process {
foreach ( $IdentityItem in $Identity ) {
Get-ADGroupMembership $IdentityItem -recursive:$Recursive
}
}
I've also added this script as a public gist on github in case something needs fixing or if I add new features.
Get-ADPrincipalGroupMembership -Identity "jyothi"

How to Load Component Services/DCOM Config SnapIn

I have a PS script to do some DCOM configuration. It works fine as long as I have the Component Services/DCOM Config snapin loaded. I want to load that programmatically so I can do all of this as part of an install package. Does anyone know how to do it? I don't know the name of the snapin to add/import.
To load the snapin I run comexp.msc -32 and click Component Services, Computers, My Computer, DCOM Configuration.
Thanks
I faced a similar problem. I couldn't find a way of loading Component services on the DCOM Config spapIn. But I found a workaround to add the user the Default DCOM Launch and Activation permissions using this powershell script:
https://www.peppercrew.nl/index.php/2012/03/set-dcom-remote-access-via-powershell/
That way, you don't need to assign the user to that particular DCOM App.
Hope this help
This is the powershell script:
PARAM(
[string]$Principal = $(throw "`nMissing -Principal DOMAIN\Group"),
$Computers = $(throw "`nMissing -Computers ('server01','server02')"))
# USAGE:
# .\Set-RemotePermission-DCOM.ps1 -Principal "DOMAIN\" -Computers ('', '',...)
#
# EXAMPLE:
# .\Set-RemotePermission-DCOM.ps1 -Principal "DOMAIN\LG-Citrix-Admins" -Computers ('CTX_DC001', 'CTX_DC002')
#
# Inspired by Karl Mitschke's post:
# http://unlockpowershell.wordpress.com/2009/11/20/script-remote-dcom-wmi-access-for-a-domain-user/
#
# And inspired Brad Turner's post:
# http://social.technet.microsoft.com/Forums/en-US/ilm2/thread/5db2707c-87c9-4bb2-a0eb-912363e2814a/
function get-sid
{
PARAM ($DSIdentity)
$ID = new-object System.Security.Principal.NTAccount($DSIdentity)
return $ID.Translate( [System.Security.Principal.SecurityIdentifier] ).toString()
}
$sid = get-sid $Principal
#DefaultLaunchPermission - Local Launch, Remote Launch, Local Activation, Remote Activation
$DCOMSDDLDefaultLaunchPermission = "A;;CCDCLCSWRP;;;$sid"
#DefaultAccessPermision - Local Access, Remote Access
$DCOMSDDLDefaultAccessPermision = "A;;CCDCLC;;;$sid"
#PartialMatch
$DCOMSDDLPartialMatch = "A;;\w+;;;$sid"
foreach ($strcomputer in $computers)
{
write-host "`nWorking on $strcomputer with principal $Principal ($sid):"
# Get the respective binary values of the DCOM registry entries
$Reg = [WMIClass]"\\$strcomputer\root\default:StdRegProv"
$DCOMDefaultLaunchPermission = $Reg.GetBinaryValue(2147483650,"software\microsoft\ole","DefaultLaunchPermission").uValue
$DCOMDefaultAccessPermission = $Reg.GetBinaryValue(2147483650,"software\microsoft\ole","DefaultAccessPermission").uValue
# Convert the current permissions to SDDL
write-host "`tConverting current permissions to SDDL format..."
$converter = new-object system.management.ManagementClass Win32_SecurityDescriptorHelper
$CurrentDCOMSDDLDefaultLaunchPermission = $converter.BinarySDToSDDL($DCOMDefaultLaunchPermission)
$CurrentDCOMSDDLDefaultAccessPermission = $converter.BinarySDToSDDL($DCOMDefaultAccessPermission)
# Build the new permissions
if (($CurrentDCOMSDDLDefaultLaunchPermission.SDDL -match $DCOMSDDLPartialMatch) -and ($CurrentDCOMSDDLDefaultLaunchPermission.SDDL -notmatch $DCOMSDDLDefaultLaunchPermission))
{
$NewDCOMSDDLDefaultLaunchPermission = $CurrentDCOMSDDLDefaultLaunchPermission.SDDL -replace $DCOMSDDLPartialMatch, $DCOMSDDLDefaultLaunchPermission
}
else
{
$NewDCOMSDDLDefaultLaunchPermission = $CurrentDCOMSDDLDefaultLaunchPermission.SDDL + "(" + $DCOMSDDLDefaultLaunchPermission + ")"
}
if (($CurrentDCOMSDDLDefaultAccessPermission.SDDL -match $DCOMSDDLPartialMatch) -and ($CurrentDCOMSDDLDefaultAccessPermission.SDDL -notmatch $DCOMSDDLDefaultAccessPermision))
{
$NewDCOMSDDLDefaultAccessPermission = $CurrentDCOMSDDLDefaultAccessPermission.SDDL -replace $DCOMSDDLPartialMatch, $DCOMSDDLDefaultAccessPermision
}
else
{
$NewDCOMSDDLDefaultAccessPermission = $CurrentDCOMSDDLDefaultAccessPermission.SDDL + "(" + $DCOMSDDLDefaultAccessPermision + ")"
}
# Convert SDDL back to Binary
write-host "`tConverting SDDL back into binary form..."
$DCOMbinarySDDefaultLaunchPermission = $converter.SDDLToBinarySD($NewDCOMSDDLDefaultLaunchPermission)
$DCOMconvertedPermissionDefaultLaunchPermission = ,$DCOMbinarySDDefaultLaunchPermission.BinarySD
$DCOMbinarySDDefaultAccessPermission = $converter.SDDLToBinarySD($NewDCOMSDDLDefaultAccessPermission)
$DCOMconvertedPermissionsDefaultAccessPermission = ,$DCOMbinarySDDefaultAccessPermission.BinarySD
# Apply the changes
write-host "`tApplying changes..."
if ($CurrentDCOMSDDLDefaultLaunchPermission.SDDL -match $DCOMSDDLDefaultLaunchPermission)
{
write-host "`t`tCurrent DefaultLaunchPermission matches desired value."
}
else
{
$result = $Reg.SetBinaryValue(2147483650,"software\microsoft\ole","DefaultLaunchPermission", $DCOMbinarySDDefaultLaunchPermission.binarySD)
if($result.ReturnValue='0'){write-host " Applied DefaultLaunchPermission complete."}
}
if ($CurrentDCOMSDDLDefaultAccessPermission.SDDL -match $DCOMSDDLDefaultAccessPermision)
{
write-host "`t`tCurrent DefaultAccessPermission matches desired value."
}
else
{
$result = $Reg.SetBinaryValue(2147483650,"software\microsoft\ole","DefaultAccessPermission", $DCOMbinarySDDefaultAccessPermission.binarySD)
if($result.ReturnValue='0'){write-host " Applied DefaultAccessPermission complete."}
}
}
#----------------------------------------------------------------------------------------------------------
trap
{
$exMessage = $_.Exception.Message
if($exMessage.StartsWith("L:"))
{write-host "`n" $exMessage.substring(2) "`n" -foregroundcolor white -backgroundcolor darkblue}
else {write-host "`nError: " $exMessage "`n" -foregroundcolor white -backgroundcolor darkred}
Exit
}
#----------------------------------------------------------------------------------------------------------
I faced the same issue and, I believe, it's because there's no equivalent 64-bit registry entry so PowerShell doesn't see it. Launching mmc compexp.msc /32 and expanding DCOM Config seems to create the entry in the background.
The work-around is to manually add the 64-bit AppID yourself which is simply done by the following code,
$appGUID = 'YOUR_APPNAME_OR_GUID'
New-PSDrive -PSProvider Registry -Name HKCR -Root HKEY_CLASSES_ROOT
New-Item -Path HKCR:\AppID\$appGUID -Value $appGUID
#New-Item -Path HKCR:\Wow6432Node\AppID\$appGUID -Value $appGUID
Remove-PSDrive HKCR
I've left the 32-bit location in the above code too although that should already exist. Once you run the above then PowerShell should be able to see the COM component,
Get-WMIObject -query ('SELECT * FROM Win32_DCOMApplicationSetting WHERE AppID = "' + $appGUID + '"') -EnableAllPrivileges
Hope this helps someone as it was driving me bananas for hours!

Object to hashtable key comparison

I'm looking for some help troubleshooting comparing .key values to objects.
Basically what's happening here is I'm connecting to two VMware vCenters and downloading a list of roles and putting those roles into two hash tables, then comparing them.
The problem comes down to the Process-Roles function where the comparing logic is flawed somewhere. It outputs all of the roles in both lists. I think the (-not .containskey) isn't working right. I've debugged in powerGUI and both hashtables and mstr_roles/slave_roles are all filled correctly.
The roles lists should be object lists, as they were filled with Get-VIRole.
The hash table should be object-in-key, value null lists. Is it possible to compare these two? I'm trying to check if the $role object in the roles list exists in the .key values list of the hash table.
$creds = Get-Credential
$mst = Read-Host "`n Master Server: "
$slv = Read-Host "`n Slave Server: "
$hsh_mstr_roles = #{}
$hsh_slave_roles = #{}
$mstr_roles = ""
$slave_roles = ""
Get-Roles -MasterServer $mst -SlaveServer $slv
Process-Roles
.
function Get-Roles() {
Param(
[Parameter(Mandatory=$True,Position=0)]
[string]$MasterServer,
[Parameter(Mandatory=$True,Position=1)]
[string]$SlaveServer
)
#Get Master Roles
Connect-VIServer $MasterServer -Credential $creds
$mstr_roles = Get-VIrole
foreach ($role in $mstr_roles) {
$hsh_mstr_roles.add($role, $null)
}
Disconnect-VIServer $MasterServer -Confirm:$false
#Get Slave Roles
Connect-VIServer $SlaveServer -Credential $creds
$slave_roles = Get-VIrole
foreach ($role in $slave_roles) {
$hsh_slave_roles.add($role, $null)
}
Disconnect-VIServer $SlaveServer -Confirm:$false
Write-Host "`n + Retrieved Roles Successfully"
}
.
function Process-Roles () {
#Get Roles on Master NOT ON SLAVE
Write-Host "`n"
foreach ($role in $mstr_roles){
if(-not $hsh_slave_roles.containsKey($role)){
Write-Host $role "doesn't exist on slave"
}
}
#Get Roles on Slave NOT ON MASTER
foreach ($role in $slave_roles){
if(-not $hsh_mstr_roles.containsKey($role)){
Write-Host $role "doesn't exist on master"
}
}
Write-Host "`n + Processed Roles Successfully"
}
The easiest way to do this is by finding the complement to one of the two sets of Keys that each hashtable has, using -notcontains:
function Process-Roles {
param(
[hashtable]$MasterRoles,
[hashtable]$SlaveRoles
)
# Complement to slave roles (those ONLY in $MasterRoles)
$MasterRoles.Keys |Where-Object { $SlaveRoles -notcontains $_ }|ForEach-Object {
Write-Host "$_ not in Slave Roles"
}
# and the other way around (those ONLY in $SlaveRoles)
$SlaveRoles.Keys |Where-Object { $MasterRoles -notcontains $_ }|ForEach-Object {
Write-Host "$_ not in Master Roles"
}
}
I'll have to add that your way of working with variables in different scopes is sub-optimal.
Define the parameters that the function needs in order to "do its job"
Return output from your functions where it make sense (any Get-* function should at least)
Depend on the Global and Script scopes as little as possible, preferably not at all
I would go with something like this instead:
Add a Credential parameter to the Get-Roles function and return the results rather than modifying a variable in a parent scope (here, using a Hashtable of role categories):
function Get-Roles {
Param(
[Parameter(Mandatory=$True,Position=0)]
[string]$MasterServer,
[Parameter(Mandatory=$True,Position=1)]
[string]$SlaveServer,
[Parameter(Mandatory=$True,Position=2)]
[pscredential]$Credential
)
$DiscoveredRoles = #{}
# Get Master Roles
Connect-VIServer $MasterServer -Credential $Credential
$DiscoveredRoles["MasterRoles"] = Get-VIRole
Disconnect-VIServer $MasterServer -Confirm:$false
#Get Slave Roles
Connect-VIServer $SlaveServer -Credential $Credential
$DiscoveredRoles["SlaveRoles"] = Get-VIrole
Disconnect-VIServer $SlaveServer -Confirm:$false
Write-Verbose "`n + Retrieved Roles Successfully"
return $DiscoveredRoles
}
Define parameters for the Process-Roles function, that match the hashtable you expect to generate from Get-Roles and do the same comparison of the role names as above, only this time we grab them directly from the Role objects:
function Process-Roles {
param(
[Parameter(Mandatory=$true)]
[ValidateScript({ $_.ContainsKey("MasterRoles") -and $_.ContainsKey("SlaveRoles") })]
[hashtable]$RoleTable
)
$MasterRoleNames = $RoleTable["MasterRoles"] |Select-Object -ExpandProperty Name
$SlaveRoleNames = $RoleTable["SlaveRoles"] |Select-Object -ExpandProperty Name
$MasterRoleNames |Where-Object { $SlaveRoleNames -notcontains $_ } |ForEach-Object {
Write-Host "$_ doesn't exist on slave"
}
$SlaveRoleNames |Where-Object { $MasterRoleNames -notcontains $_ } |ForEach-Object {
Write-Host "$_ doesn't exist on Master"
}
Write-Host "`n + Processed Roles Successfully"
}
Update your executing script with the new parameters:
$creds = Get-Credential
$MasterServer = Read-Host "`n Master Server: "
$SlaveServer = Read-Host "`n Slave Server: "
$RoleTable = Get-Roles -MasterServer $MasterServer -SlaveServer $SlaveServer -Credential $creds
Process-Roles -RoleTable $RoleTable
Next step would be to add pipeline support to the Process-Roles function, converting Write-Host statements to Write-Verbose and adding error handling, but I'll leave that as an exercise to OP :-)
try:
if(!$hsh_slave_roles.containsKey($role))