Get-Choice

I came across this little function today whilst doing some house cleaning.   It’s a function that will show a message prompt and uses a switch statement to control what actions are taken.  It’s an unsupported Microsoft script but no idea where it came from.

Function GetChoice {
 #Prompt message
 $Caption = “Restart the computer.”
 $Message = “It will take effect after restart,  do you want to restart right now?”
 $Choices = [System.Management.Automation.Host.ChoiceDescription[]] @(“&Yes”,“&No”)
 [Int]$DefaultChoice = 0
 $ChoiceRTN = $Host.UI.PromptForChoice($Caption$Message, $Choices, $DefaultChoice)
 Switch ($ChoiceRTN){
  0  { shutdown -t 0 -r }
  1 {break}
}
}

Anyway, clearly this is ripe for customisation.  Step one lets make the function name conform to the PowerShell convention by adding a hyphen
Function Get-Choice

Now lets look at what else might be useful …..

Well the caption and message and even the choices could be made into parameters so you can change the ‘choices’

param (
 $Caption = “Restart the computer.”,
$Message = “It will take effect after restart, do you want to restart right now?”,
$Choices = @(“&Yes”,“&No”,“&Maybe”)
)
change the line that instantiated the $choices variable to

$Choices = [System.Management.Automation.Host.ChoiceDescription[]] $Choices

Hopefully you can see the flaw in the logic now though – the switch statement can’t be customised, well not easily I could pass in a script block but there’s little point in that.

But you could either directly return the choice value as an integer

$Host.UI.PromptForChoice($Caption, $Message, $Choices, $DefaultChoice)

Or return the value

$Choices[$Host.UI.PromptForChoice($Caption, $Message, $Choices, $DefaultChoice)].Label

Advertisements

Preventing non admin accounts being added to a group using Active Roles

Here is a function I use to prevent non admin users being added to a group

You can modify this to get the user object and then take appropriate action –

In my case the DN was CN=admin- so I realised I didn;t need to actuially bind to the user object but I left in my original code should I need it later – you can see my original get-qaduser call is commented out

# $member will be the DN
# $user = Get-QADUser $member

NOTE: if you are allowing changes externally to the group as is the case in my environment you also need an onPostModify ($Request)  function to deal with changes syncing in from other DCs.

function onPreModify($Request)
{
# this function handles updates carried out in ARS – prevents non admin user being added
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> Check if this was a group modification”
if ($Request.class -eq “group”)
{
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> Group object modified”
if ( $(Get-IsAttributeModified -AttributeName ‘member’ -Request $Request) )
{
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> Checking if member attribute was modified”
for ($i = 0; $i -lt $Request.PropertyCount; $i++) {
$item = $Request.Item($i)
$Name = $item.Name
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> item = $($item) name = $($name)”
# enumerate the objects attributes until we locate the “member” attribute
if ($Name -eq “member”)
{
# check that the member attribute was modified
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> Found Member Attribute so processing ”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> item control code = $($item.ControlCode) ”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> Constant APEND =  $($Constants.ADS_PROPERTY_APPEND) ”
if($item.ControlCode -eq $Constants.ADS_PROPERTY_APPEND )
{
# iterate through the group members
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> item control code was APPEND so processing…… ”
foreach ($member in $item.Values) {
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> member = $($member)”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> left(member,9) = $($member.substring(0,9))”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> left(member,10) = $($member.substring(0,10))”
# $member will be the DN
#$user = Get-QADUser $member
#if ( $($user.sAMAccountName).substring(0,6) -ne “admin-” )
if ( ( $member.substring(0,9) -ne “CN=Admin-” ) -and  ( $member.substring(0,10) -ne “CN=Admin -” ) -and ( $member.substring(0,7) -ne “CN=svc-” ) ) {
$groupName = Get-AttributeValue -AttributeName “Name” -ADSIObject $Request
$groupDN = Get-AttributeValue -AttributeName “DN” -ADSIObject $Request
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PreModify >>> >> Grp Name : $($groupname) DN: $($groupDN)”
throw “******************************************************`nYou cannot add non ‘admin-<logon name>’ accounts `nto this group`n******************************************************`nNOTE: Script Policy Checks the DN of the user not the logon name”
}
} # end for each
} # end if property changed
} # end if member found
} # end for iterate group members
} # end Check if group members updated
} # end if modified object was a group
} # end function

 

function onPostModify ($Request)
{
# this fucntion deals with changes made outside of ARS…
if ($Request.class -eq “group”)
{
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> Group object modified”
if ( $(Get-IsAttributeModified -AttributeName ‘member’ -Request $Request) )
{
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> Group membership changed”
for ($i = 0; $i -lt $Request.PropertyCount; $i++) {
$item = $Request.Item($i)
$Name = $item.Name
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> item = $($item) name = $($name)”
# enumerate the objects attributes until we locate the “member” attribute
if ($Name -eq “member”)
{
# check that the member attribute was modified
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> Found Member Attribute so processing ”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> item control code = $($item.ControlCode) ”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> Constant ADS_PROPERTY_APPEND =  $($Constants.ADS_PROPERTY_APPEND) ”
if( $item.ControlCode -eq $Constants.ADS_PROPERTY_APPEND )
{
# iterate through the group members
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> item control code was APPEND so processing…… ”
foreach ($member in $item.Values) {
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> member = $($member)”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> left(member,9) = $($member.substring(0,9))”
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only PostModify >>> left(member,10) = $($member.substring(0,10))”
# $member will be the DN
if ( ( $member.substring(0,9) -ne “CN=Admin-” ) -and  ( $member.substring(0,10) -ne “CN=Admin -” ) -and ( $member.substring(0,7) -ne “CN=svc-” ) ) {
$groupName = Get-AttributeValue -AttributeName “Name” -ADSIObject $Request
Out-DebugString -verbosity 9 -str “group-Enforce-Admin-Only postModify >>> GroupName = $($groupname)”
Remove-QADGroupMember -Member $member -Proxy -Identity $groupName -Control @{OperationReason=”Group – Enforce Admin Users Only_v1.1″}
} # end check if the user is not an admin- user
} # end processing each member added
} # end if members were added – ADS_PROPERTY_APPEND = 3
} # end if member found
} # end for iterate group members updated
} # end check that the group membership was updated
} # end check that request object was a group object
}

I’ve included my debug function for you here too – this writes to the event log – these go at the top of the script file

function onInit($Context)
{
$par01 = $context.AddParameter(“debugging”)
$par01.MultiValued = $false
$par01.PossibleValues = “0”, “1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”
$par01.DefaultValue = “1”
$par01.Description = “Debugging EventLog Level where: 0 is no debugging; 9 is the most verbose; 1 is the least verbose”
$par01.Required = $false
#
#
}

function Out-DebugString([int]$verbosity, [string]$str )
{
# outputs debug info to the EDM event log
if ( [string]$PolicyEntry.Parameter(“debugging”) -ne ‘0’ )
{
$strDebuggingSwitch = [string]$PolicyEntry.Parameter(“debugging”)
if ( $verbosity -le [int]$strDebuggingSwitch )
{
$EventLog.ReportEvent(2,$str)
}
}

function Get-IsAttributeModified ([string]$AttributeName, $Request)
{
$objEntry = $Request.GetPropertyItem($AttributeName, $Constants.ADSTYPE_CASE_IGNORE_STRING)
if ($objEntry -eq $null) { return $false }
if ($objEntry.ControlCode -eq 0) { return $false }
return $true
} #– Get-IsAttributeModified

Cleanup broken links between AD objects

I use the seeAlso and Secretary attributes to establish a relationship between a users personal employee account and any service accounts they “own”.  What I subsequently discovered is that if the service account is deleted then the “link” is not deleted and worse the SD tell me that when a user with one of these broken “link” tries to delegate access to their mailbox they get an error and are unable to delegate. delegateerror

A deleted link will show up in the CN=Deleted Objects container and be renamed as follows:

CN=<ObjectOriginalName>ADEL:<GUID>,CN=Deleted Objects,DC=ad,DC=com

Note the ADEL in the name.  I use this as an easy way to search for the deleted “linked” object in a clean up script.  I use this LDAP filter to locate all of the accounts that have the links configured,

$ownerldapFilter = “(&(employeeID=*)(sAMAccountType=805306368)(seeAlso=*))”

I get all of the user objects with this line of code where $searchroot has been initialised to the OU in AD storing the user objects I’m interested in

$Users = get-qaduser -SearchRoot $searchroot -LdapFilter $ownerldapFilter -IncludedProperties seeAlso -sizelimit 0 -Proxy

For Each user object I loop through the values in the seeAlso attribute and check if the string OADEL

if ( $serviceAccountDN.indexOf(“0ADEL:”) -gt 0 ) {

If I find it I set a flag indicating to the script that I need to update the attribute value.  If the link does not contain the string I store it in a temporary array.  Once I’ve been through all of the links I check if the update flag was set and if it was I update the attribute value with the stored values.  Anyway here’s the full code you can use as a template for your own clean up script.

#===========================================================================================================================================
$ScriptName    = “User-ManageSeeAlsoAndSecretaryAttributes”
$scriptVersion = “1.0”
$taskDN = “CN=Clear Deleted Account Links,CN=Data Integrity,CN=Scheduled Tasks,CN=Server Configuration,CN=Configuration”
#===========================================================================================================================================
#
# This scheduled task checks for broken account links – where the secretary or seeAlso attributes contain deleted objects
#
# Version 1.0 20/05/2015 Initial version
#
#region Helper Functions
function Send-Alert {
$arrstrAlertMessage = Get-Content $logfilepath
$strAlertMessage = “”
$arrstrAlertMessage | % { $strAlertMessage += “$_   `n” }
Send-MailMessage -Body $strAlertMessage -SmtpServer $smtpServer -Subject $subject -To $toMail -From $fromMail
throw “ERROR in last run – please check email for error report”
}
function GetTaskParameters {
try {
$Task.DirObj.GetInfo()
$Task.DirObj.GetInfoEx(@(“edsaParameters”),0)
$strParameters = $Task.DirObj.Get(“edsaParameters”)
}
catch {
try {
$Task = Get-QADObject -Identity $TaskDN  -Proxy -IncludedProperties edsaParameters
$strParameters = $Task.edsaParameters
}
catch {
$stream.WriteLine(“FATAL ERROR: Failed to get script parameters”)
$stream.Close()
throw “FATAL ERROR: Failed to get script parameters”
}
}
$strParameters = ‘<parameters>’ + $strParameters + ‘</parameters>’
if( $Task ){ if ( $Task -is [IDisposable] ){ try { $Task.Dispose() } catch{} } }; Remove-Variable -Name Task -ErrorAction SilentlyContinue
return $strParameters
}
#endregion Helper Functions
#region Initialise script variables
$ownerldapFilter = “(&(employeeID=*)(sAMAccountType=805306368)(seeAlso=*))”
$resourceldapFilter = “(&(sAMAccountType=805306368)(secretary=*))”
# Set up a log file for debug and reporting
$logfilename = “Account RelationShip Checks”
$logDirectory = “c:\scripts\Identity Management\Logs\AccountRelationshipChecks”
$logfilepath = $logDirectory+”\”+$logfilename+”_”+$(Get-Date -Format yyyy-MM-dd)+”.log”
# Create a UTF8Encoding object so we can force the BOM – so that Umlauts display correctly
$utf8 = New-Object System.Text.UTF8Encoding($true)
$stream = New-Object system.IO.StreamWriter($logfilepath,$true,$utf8) # True appends to the file
#endregion Initialise script variables
#region Get Task Parameters
$strParameters          = GetTaskParameters
$xmlParameters          = [xml]$strParameters
$smtpServer = [string]$xmlParameters.parameters.smtpServer
if ( ($smtpServer -eq $null) -or ($smtpServer.length -le 10) ) { $smtpServer = “smtp.myAD.com”}
$subject = [string]$xmlParameters.parameters.subject
if ( ($subject -eq $null ) -or ($subject.length -le 0 )) { $subject = “ERROR CLEARING ACCOUNT RELATIONSHIP – PLEASE INVESTIGATE URGENTLY” }
$toMail = [string]$xmlParameters.parameters.toMail
if (( $toMail -eq $null ) -or ($toMail.length -le 0 ) ) { $toMail = “lee.andrews@MyDomain.com” }
$tomail = $tomail.split(“,”)
$fromMail = [string]$xmlParameters.parameters.fromMail
if ( ( $fromMail -eq $null ) -or ($fromMail.length -le 0 )) { $fromMail = “lee.andrews@MyDomain.com” }
$loglevel = [int]$xmlParameters.parameters.loglevel
if ( ( $loglevel -eq $null ) -or ( $loglevel.gettype().Name -ne “int32” ) -or ( $loglevel -eq 0 ) ) { $loglevel = [int]9 }
$commitChanges = [string]$xmlParameters.parameters.CommitChanges
switch ($commitChanges) {
“Yes” {
$whatIf = $false
break
}
“No” {
$whatIf = $true
break
}
default {
$whatIf = $false
break
}
}
$searchroot = [string]$xmlParameters.parameters.searchroot
if ( ($searchroot -eq $null) -or ($searchroot.length -le 10) ) { $searchroot = “OU=AD Management,DC=MyAD,DC=com” }
#endregion Get Task Parameters
#region Write log header
$stream.writeline(“===========================================================================” )
$stream.writeline(“ScriptName     : $ScriptName” )
$stream.writeline(“Script Version : $scriptVersion” )
$stream.writeline(“Date           : $(Get-Date)” )
$stream.writeline(“—————————————————————————” )
$stream.writeline(“Parameters:” )
$stream.writeline(“`tOwner LDAP Filter    : $ownerldapFilter” )
$stream.writeline(“`tResource LDAP Filter : $resourceldapFilter” )
$stream.writeline(“`tSMTP Server          : $smtpServer” )
$stream.writeline(“`tSubject              : $subject” )
$stream.writeline(“`ttoMail               : $toMail” )
$stream.writeline(“`tfromMail             : $fromMail” )
$stream.writeline(“`tcommitChanges        : $commitChanges” )
$stream.writeline(“`tsearchroot           : $searchroot” )
$stream.writeline(“`tloglevel             : $loglevel” )
$stream.writeline(“===========================================================================” )
#endregion Write log header
#region Look for broken seeAlso links
$stream.writeline(“get-qaduser -SearchRoot  $searchroot -LdapFilter $ownerldapFilter -IncludedProperties seeAlso -sizelimit 0 -Proxy”)
$Users = get-qaduser -SearchRoot  $searchroot -LdapFilter $ownerldapFilter -IncludedProperties seeAlso -sizelimit 0 -Proxy
$seeAlsoUpdated = 0
$stream.writeline(“Found: $($users.Count) users with seeAlso attribute set”)
$stream.writeline(“===========================================================================” )
forEach ( $user in $Users ) {
$seealso = @()
$userNameWrittenToLog = $false
$seeAlsoRequiresUpdate = $false
ForEach ( $serviceAccountDN in $user.seeAlso ) {
if ( $serviceAccountDN.indexOf(“0ADEL:”) -gt  0 ) {
$seeAlsoRequiresUpdate = $true
$seeAlsoUpdated++
if ( $loglevel -ge 8 ) {
if ( $userNameWrittenToLog -eq $false ) {
$stream.writeline(“Processing $($user.NTAccountName)”)
$userNameWrittenToLog = $true
}
$stream.writeline(“Removing : $serviceAccountDN”)
}
}
else {
$seealso += $serviceAccountDN
if ( $loglevel -ge 9 ) {
if ( $userNameWrittenToLog -eq $false ) {
$stream.writeline(“Processing $($user.NTAccountName)”)
$userNameWrittenToLog = $true
}
$stream.writeline(“Keeping  :$serviceAccountDN”)
}
}
}
if ( $seeAlsoRequiresUpdate -eq $true ) {
try { Set-QADUser -Identity $user.DN -ObjectAttributes @{seealso=$seealso} -Proxy -WhatIf:$whatIf -Control @{OperationReason=”$($ScriptName)_v$($scriptVersion)”} }
catch { $stream.writeline(“ERROR REMOVING seeAlso from: $($user.NTAccountName)” ) }
}
if ( ( $loglevel -ge 8 ) -and ( $userNameWrittenToLog -eq $true ) ) { $stream.writeline(“—————————————————————————” ) }
}
$stream.writeline(“Updated seeAlso attribute on $seeAlsoUpdated users”)
#endregion Look for broken seeAlso links
#region Look for broken secretary links
$stream.writeline(“===========================================================================” )
$Users = get-qaduser -SearchRoot  $searchroot -LdapFilter $resourceldapFilter -IncludedProperties secretary -sizelimit 0 -Proxy
$stream.writeline(“Found: $($users.Count) users with secretary attribute set”)
$stream.writeline(“===========================================================================” )
$secretaryUpdated = 0
forEach ( $user in $Users ) {
$secretary = @()
$userNameWrittenToLog = $false
$secretaryRequiresUpdate = $false
ForEach ( $ownerDN in $user.secretary ) {
if ( $ownerDN.indexOf(“0ADEL:”) -gt  0 ) {
$secretaryRequiresUpdate = $true
$secretaryUpdated++
if ( $loglevel -ge 8 ) {
if ( $userNameWrittenToLog -eq $false ) {
$stream.writeline(“Processing $($user.NTAccountName)”)
$userNameWrittenToLog = $true
}
$stream.writeline(“Removing : $ownerDN”)
}
}
else {
$secretary += $ownerDN
if ( $loglevel -ge 9 ) {
if ( $userNameWrittenToLog -eq $false ) {
$stream.writeline(“Processing $($user.NTAccountName)”)
$userNameWrittenToLog = $true
}
$stream.writeline(“Keeping  :$ownerDN”)
}
}
}
if ( $secretaryRequiresUpdate -eq $true ) {
try { Set-QADUser -Identity $user.DN -ObjectAttributes @{secretary=$secretary} -Control @{OperationReason=”$($ScriptName)_v$($scriptVersion)”} -Proxy -WhatIf:$whatIf }
catch { $stream.writeline(“ERROR REMOVING secretary from: $($user.NTAccountName)” ) }
}
if ( ( $loglevel -ge 8 ) -and ( $userNameWrittenToLog -eq $true ) ) { $stream.writeline(“—————————————————————————” ) }
}
$stream.writeline(“Updated ‘secretary’ attribute on $secretaryUpdated users”)
#endregion Look for broken secretary links
#region Close log file
$stream.writeline(“===========================================================================” )
$stream.writeline(“Script Run Finish : $(Get-Date)” )
$stream.writeline(“===========================================================================” )
$stream.close()
#endregion Close log file

Get-NearestDC

I probably should have googled this and found a more elegant solution but given my last post  I just cut out the bits I needed for this solution to find a local DC because had a need to use a specific DC with an exchange cmdlet.

I also added in the ability to choose a specific OS because in my case I have older 2003 DCs and 2012 DCs and I wanted to force the use of a 2012 DC.

For speed I found that using the forest to determine the sites and then getting the DCs in site was much quicker that directly accessing the DC list in the $mydomain object.

Function Get-NearestDC {
 $myDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
 $myDomainName = “<FQDN>”
 $Forest = $myDomain.Forest
 $sites = $Forest.Sites
 $mySIte = $(Get-itemProperty “hklm:\system\CurrentControlSet\Services\Netlogon\Parameters”).DynamicSiteName
 ForEach ( $site in $sites ) {
  if ( ( $site.name -eq $mySIte ) ) {
   ForEach ( $server in $site.servers ) {
    if ( ( $server.OSVersion -eq “Windows Server 2012 R2 Standard” ) -and ( $server.Domain.name -eq $myDomainName ) ) {
     $dc = $server.Name
     break
          }
}
   ForEach ( $server in $site.servers ) {
    if ( $server.Domain -eq $myDomainName ) {
     $dc = $server.Name
     break
          }
}
}
}
 Return $dc
}

getting Domain Controller info

Next time someone asks you for the IP address or name of al the DCs in your environment try using [System.DirectoryServices.ActiveDirectory.Domain]  to enumerate it for you

This little script just enumerates the attribute values and displays them on screen.  You could easily build this into a full report and export it into any format you like/

cls
$padright = 90
$myCount = 0
$myDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
# list forest
$Forest = $myDomain.Forest
$Sites = $Forest.Sites
Write-Host “Forest Name….: $($Forest.RootDomain)”
Write-Host “Forest Mode….: $($Forest.ForestMode)”
Write-Host “No. of sites…: $($Sites.Count)”
$GCs = $Forest.GlobalCatalogs
$SchemaMaster = $Forest.SchemaRoleOwner
$DomainNameMaster = $Forest.NamingRoleOwner
Write-Host “”
Write-Host “Domain Details”
$Domains = $Forest.Domains
$forestRootDomain = $Forest.Name
$forestDomainInfo = @{}
ForEach ($Domain in $Domains) {
$domainFSMOs = “” | Select PDCe,RIDMaster,InfrastructureMaster
Write-Host “”.padright($padright,“=”)
Write-Host “Domain : $($Domain.Name)”
Write-Host “”.padright($padright,“=”)
ForEach ( $DomainController in $Domain.DomainControllers ) {
Write-Host “DC Name….: $($DomainController.Name)”Write-Host “OS Version.: $($DomainController.OSVersion)”
Write-Host “Site…….: $($DomainController.SiteName)”
Write-Host “IP Address.: $($DomainController.IPAddress)”
if ( $($DomainController.Roles).Count -gt 0 ) {
Write-Host “DC Roles:”
forEach ($role in $DomainController.Roles) {
switch ($role) {
“PdcRole” {
$domainFSMOs.PDCe = $DomainController.Name
break
}
“RidRole” {
$domainFSMOs.RIDMaster = $DomainController.Name
break
}
“InfrastructureRole” {
$domainFSMOs.InfrastructureMaster = $DomainController.Name
}
}
$rolestring = $role.toString().toUpper().Substring(0,$role.toString().toUpper().indexof(‘ROLE’))Write-Host “`t $RoleString”
}
}
Write-Host “”.padright($padright,“=”)
}
$forestDomainInfo.Add($($Domain.Name),$domainFSMOs)

}

Write-Host “”.padright($padright,“-“)
Write-Host “”.padright($padright,“=”)
Write-Host “Forest Name….: $($Forest.RootDomain)”
Write-Host “Forest Mode….: $($Forest.ForestMode)”
Write-Host “$($GCs.count) Global Catalogue Servers”
Write-Host “”.padright($padright,“=”)
Write-Host “$($Sites.Count) Sites:”
ForEach ( $Site in $Sites ) { Write-Host “`t $site” }
Write-Host “”.padright($padright,“=”)
Write-Host “”
Write-Host “Forest Root FSMO Holders”
Write-Host “”
Write-Host “Schema Master ……. : $SchemaMaster”
Write-Host “Domain Naming Master. : $DomainNameMaster”
Write-Host “Domain Naming Master. : $DomainNameMaster”
Write-Host “”.padright($padright,“=”)
Write-Host “Child Domain FSMO Holders”
Write-Host “”.padright($padright,“=”)
ForEach ( $childDomain in $forestDomainInfo.GetEnumerator() ) {
Write-Host “Domain: $($childDomain.name)”
Write-Host “PDCe…………. : $($childDomain.Value.PDCe)”
Write-Host “RID Master……. : $($childDomain.Value.RIDMaster)”
Write-Host “IM Master…….. : $($childDomain.Value.InfrastructureMaster)”
Write-Host “”.padright($padright,“-“)
}
Write-Host “”.padright($padright,“=”)

Uploading a Jpeg into AD

This final function will upload a suitably sized Jpeg into AD. It’s not as simple as loading the jpeg into the thumbnail attribute you have to byte encode it. This is however as easy as casting the variable type to an array of bytes ( variable type [byte[]] ) and letting PowerShell do the heavy lifting for you as always.

The only two lines of code you need are the [byte[]]$jpg = Get-Content $jpegFile -encoding byte and the Set-QADUser -Identity $guid -ObjectAttributes @{thumbnailPhoto=$jpg

I’ve used the GUID to identify the target user but you can use anything in your version of the function obviously. The function also writes to a log file for you.

This was quite an early script for me and I’ve not set the parameters to be mandatory or error checked them, see my other posts on creating fucntions ( this probably breaks all my recommendations 🙂 ) The function just assumes the parameters are always going to be correct. I’d recommend you add some error handling and these functions are probably due a rewrite but as the 3 functions I just posted are currently part of a much larger script I don’t have to worry about the error handling as I know what’s being passed to each function but it’s still good practice to add error handling in all of your functions then you can just copy and paste them into other scripts safe in the knowledge that they will work and deal with all errors.

Function Update-Thumbnail {
 param (
  [string]$jpegFile,
  [string]$guid,
  [string]$sAMAccountName,
  [string]$logfile
 )
# check if the GUID was passed as a parameter - if not we can't upload the picture
 if ( (!($guid)) -or ( $guid.Length -le 0) ) {Return $false	}
# convert the picture file - byte encoded
 if ($jpg) { if ($jpg -is [IDisposable]) {try {$jpg.Dispose() | Out-Null } catch{}}; Remove-Variable -Name jpg -ErrorAction SilentlyContinue | Out-Null }
 Try { [byte[]]$jpg = Get-Content $jpegFile -encoding byte } Catch{}
 If ( $jpg )	{
 $logAction = " thumbnail updated "
 Try {Set-QADUser -Identity $guid -ObjectAttributes @{thumbnailPhoto=$jpg} -ErrorAction SilentlyContinue | Out-Null } Catch{$logAction = " thumbnail failed to update " } 
  $($(Get-Date).tostring() + " " + $(RPAD -stringToPad $sAMAccountName -paddedLength 20) + $logAction )| out-file $logfile -append -encoding default
 }
 if ($jpg) { if ($jpg -is [IDisposable]) {try {$jpg.Dispose() | Out-Null } catch{}}; Remove-Variable -Name jpg -ErrorAction SilentlyContinue | Out-Null }
 Return $true
}