SUBINACL – and needing to migrate ACLs for a foreign forest

So here I was today working an a folder a server in a foreign forest, trying to duplicate the ACLs of the source forest to the target forest. Both forests contained groups with the same RDNs and SAM account names.

My first choice was the obvious and trusty SUBINACL. So I entered the command below expecting it to start chugging away.

subinacl /subdirectories "x:\foldertochange\*.*" /migratetodomain=source=target

Instead of the nice chug of folders being modified I got an error saying the syntax was wrong. Quick check – Yep, using an elevated prompt. Spelling good – check. Trust between the forest was still going. Switching the command to verbose mode I got a different result:

subinacl /verbose=1 /subdirectories "x:\foldertochange\*.*" /migratetodomain=source=target

1722 Unexpected error  NetUserModalsGet on server \\DC-IN-TARGET
Error finding domain name : 1722 The RPC server is unavailable

The fix is simple. On the source data server I opened the HOSTS file and added an entry for the target DC it was trying to talk to. Re-ran the command and 211,000+ objects later everything was golden.

Advertisements

Office365 and dirsync – the multiple accounts with the same UPN/mail/proxyaddresses value

For everyone who is working on an Office365 email deployment and using dirsync you are probably familiar with dirsync errors and trying to find the duplicated proxy addresses within AD. For everyone who hasn’t started one of these there are several things which must be unique within the FOREST. Proxy addresses is one of those things that must be unique within the directory. Dir sync will find instances of duplicated proxy addresses and will error on those objects.

Running into this at work I decided to see if someone had done the heavy-lifting of writing a script before me to find duplicated proxy address, mail, and UPN values. I didn’t find anything doing a quick Google that suited my needs and wants.

What this does is it goes out and pulls all of the user, contact, and group objects within the forest, selecting the canonical name, mail, UPN, and proxy addresses values for each. It then puts all of that into a single array. A prompt is put in for what string to look for. Once that is entered a straight search through all of the collected objects is performed with the results of any matches displayed. The input loop is then repeated so I don’t have to recollect all the data each time.

# This code is deliberately inefficient on the Get-ADObject command. The purpose being so the script can be adapted for other
# duplicate searches, such as for the mail user object property being non-unique. One advantage to this code is that it looks for
# all user and contact objects and gets their UPNS, mail, and proxyaddresses values, rather than just those with homeMDB populated. I have seen some
# accounts that have had the mailboxes rudely disassociated leaving proxyaddresses values that are not searchable via EMC/EMS.
#
$domainlist= (get-adforest).domains
foreach ($d in $domainlist)
    {
        Write-host "Processing domain " $d ". Please be patient. This may take some time depending on the number of user objects."
        $userlist = get-adobject -LDAPFilter "(&(objectClass=User)(objectCategory=person))" -Server $d -properties canonicalname,proxyaddresses,mail,userprincipalname | select canonicalname,proxyaddresses,mail,userprincipalname
        $contactlist = get-adobject -LDAPFilter "objectClass=Contact" -Server $d -properties canonicalname,proxyaddresses,mail,userprincipalname | select canonicalname,proxyaddresses,mail,userprincipalname
        $grouplist = get-adobject -LDAPFilter "objectClass=group" -Server $d -properties canonicalname,proxyaddresses,mail,userprincipalname | select canonicalname,proxyaddresses,mail,userprincipalname
	    foreach ($ul in $userlist)
	        {
		        [array]$allobjs += $ul
	        }
	    foreach ($cl in $contactlist)
	        {
		        [array]$allobjs += $cl
	        }
	    foreach ($gl in $grouplist)
	        {
		    [array]$allobjs += $gl
	        }
    }
$total = $allobjs.count
write-host " "
write-host "-------------------"
write-host "Total user and contact objects collected : " $total
$count = 1
$MatchingObjs = $null
write-host " "
write-host "-------------------"
$address= read-Host "Enter search address. Hit ENTER or type exit to exit. : "
If (($address.Length -gt 0) -and ($address -ne "exit"))
    {
        Do
            {
                foreach ($ao in $allobjs)
                    {
                        Write-Progress -Activity "Scanning for" $address -PercentComplete ($count/$total * 100)
                        $MatchFound = $False
                        ForEach ($pa in $ao.ProxyAddresses) 
                            {
                                If ($pa –Match $address)
                                    {
                                        $MatchFound = $True
                                    }
                            }
                        #add matches to array 
                        If ($ao.mail –Match $address)
                            {
                                $MatchFound = $True
                            }
                        If ($ao.userprincipalname –Match $address)
                            {
                                $MatchFound = $True
                            }
                        If ($MatchFound) 
                            {
                                [array]$MatchingObjs += $ao
                            }
                        $count++
                    }
                write-host " "
                write-host "-------------------"
                Write-host "Matching objects:"
                write-host "-------------------"
                foreach ($mo in $MatchingObjs)
                    {
                        write-host $mo.canonicalname
                    }
                write-host "-------------------"
                write-host " "
                $count = 1
                $MatchingObjs = $null
                $address = read-Host "Enter search address. Hit ENTER or type exit to exit. : "
            }
        Until (($address.Length -eq 0) -or ($address -eq "exit"))
    }

Running a different Linux at home now

For years I’ve been using Ubuntu at home, but I finally decided to switch to something else.

It wasn’t one thing in particular that did it, more a collection of things. Ever since I had upgraded to the 13.04 version I had a problem with the system at boot up throwing an error. After some searching I found this was a common issue and had to do with, IIRC, the Gnome components versus some other things. I’d fix it, and then either the next version or some update would come along and re-introduce the problem. I also got tired of opening the software center and getting ads for programs when all I wanted to do was add something like gimp. Even the system updates was buggy, sometimes having to be manually started in order to get updates.

After doing a little research I decide to go with Linux Mint. So far I’ve been impressed. The install was smooth and the end result has been like a breath of fresh air. I haven’t re-installed everything yet but what I have installed has not balked or errored out.

The only thing I found frustrating, as I did with Ubuntu, was getting printers installed. I don’t blame either distro for that. Instead it is the fault of the printer manufacturers who want to hide how their things work. On that I really do wish the printer makers (I’m looking at you Epson, HP, and Brother) would get their act together and start publishing either drivers for Linux that are easy to install or the APIs so that the community could do it.

Boy have I been away or what?

I’ve been seriously bad about keeping this up to date and I’m going to try to get it going again.

To start with I’m going to try to put forward some of the scripts I’ve written for work, sanitized of course to remove data. I’m also going to try to publish some about the things I’m doing outside of work. One of those is working on some new ‘saddle bags’ for my motorcycle.

Comments Off on Boy have I been away or what? Posted in Uncategorized

Sending multiple command outputs to a single Out-GridView

I’ll admit – I’ve been very remiss in keeping this up of late. To make up for that I’m going to try to make sure to do three posts a week.

Since it’s now right at 2155 I won’t be able to do anything continuing my previous posts. However, I will provide a handy tip.

Frequently I have come across needing to get information from several accounts and put them into a spreadsheet. Often there isn’t sufficient need to build a huge script to set things up, so what I do is this.

I start with and array type variable and add my first set of values like this:

[array]$a = get-qaduser Joe | select-object logonname,lastname,firstname,accountexpires

From here I add each successive value:

$a += get-qaduser Bob | select-object logonname,lastname,firstname,accountexpires
 $a += get-qaduser Fred | select-object logonname,lastname,firstname,accountexpires

Now I can send it to Out-GridView:

$a | Out-GridView -OutputMode multiple

I now have a nice output window where I can copy the values straight to an Excel workbook!

 

Enjoy!

Interlude III : Setting Primary Group ID

Ran into an interesting situation at work today. An account had had the primary group changed from the Domain Users group to another group and then the Domain Users membership was removed. Needless to say, this caused some issues with an application that was granting rights based on membership to the Domain Users group. That got me thinking about how to set the primary group on an account without having to root around inside the ADUC utility.

Setting this is pretty simple from a PowerShell perspective. Fisrt we need to find the SID of our desired primary group. To do this we use the following:

$groupsid = (Get-ADGroup "group name").sid

This gets us the SID information in a variable where we can work with it. Notice I said “where we can work with it” versus just “in a variable”. If you look at the variable contents you’ll find we have more than just the SID.

[PS]>$groupsid | fl
BinaryLength : 28
AccountDomainSid : S-1-5-21-<SID stuff here>
Value         : S-1-5-21-<SID stuff here>-513

We actually have three values : the binary length, the domain SID the group is in, and the full SID of the group. We need to be concerned with only the Value, and specifically only with the last bit, 513. To get that portion we’re going to use two string functions – LastIndexOf and Substring. The LastIndexOf is going to give us the position of the last occurrence of the character “-” which comes just before the group ID (513). By adding one to that and using the Substring function we’ll be able to get everything to the right of that. We’ll place this into a variable that is defined as an integer value since the property on the user object has to be an integer value.

[int]$primarygroupid = $groupsid.Value.Substring($groupsid.Value.LastIndexOf("-")+1)

Finally we can update the user object with the primary group ID value

Set-ADUser "user" -Replace @{primaryGroupID=$primarygroupid}
Comments Off on Interlude III : Setting Primary Group ID Posted in Uncategorized

Interlude II : Excel with PowerShell

In my last post I finished with this code:

$xl = New-Object -ComObject "Excel.Application"
$xl.visible = $true
$xlbook = $xl.workbooks.Add()
$xlbook.worksheets.Add() | out-null
$xlsheets = $xlbook.worksheets
$xlsheet1 = $xlsheets.item(1)
$xlsheet1.name = "Report"
$xlsheet1.Cells.Item(1,1) = "Header1"
$xlsheet1.Cells.Item(1,2) = "Header2"
$xlsheet1.Cells.Item(1,3) = "Header3"
$xlbook.SaveAs("REPORT.XLSX")
$xl.quit() | Out-Null

As I said, this can be made much better looking for a report. In addition, we can do things a bit smarter to make our job easier down the road.

First off, let’s break our code up into functions.

Function NewExcelObj
     {
          Set-Variable -Name xl -Value (New-Object -ComObject "Excel.Application") -Scope 1
          $xl.visible = $true
          Set-Variable -Name xlbook -Value ($xl.workbooks.Add()) -Scope 1
         $xlbook.worksheets.Add() | out-null
     }
Function AddSheets
     {
          Set-Variable -Name xlsheet -Value ($xlbook.worksheets) -Scope 1
          Set-Variable -Name xlsheet1 -Value ($xlsheet.item(1)) -Scope 1
          $xlsheet1.name = "Report"
     }
Function AddHeaders
     {
          $xlsheet1.Cells.Item(1,1) = "Header1"
          $xlsheet1.Cells.Item(1,2) = "Header2"
          $xlsheet1.Cells.Item(1,3) = "Header3"
     }
Function CloseExcel
     {
          $xlbook.SaveAs("REPORT.XLSX")
          $xl.quit() | Out-Null
     }
# Main code
NewExcelObj
AddSheets
AddHeaders
CloseExcel

What we’ve done is moved all of our code into functions. Note we had to change how certain variables are defined.

In PowerShell there are different scopes. Variables, objects, and constants reside within the scopes. Variables (note: I’m using this as shorthand to include objects and constants as well) defined in the main script are available to all levels of the script. A variable defined within a function is normally available to just that function’s scope. When the code returns from the function the variable defined within the function is lost.

Because of that you’ll see I have redone the definitions of some of the items used previously . Instead of the normal “$Variable = value” methodology I have swapped in “Set-Variable –Name Variable –Value (value) –Scope 1”. Using this format allows me to make use of the Scope option which allows me to say that the variable is defined at the scope 1 level above the function scope, in this case the base script scope level. With the variables created that way I can proceed to make use of the variables in other functions.

This cleans up the code some, but what about the spreadsheet. It’s still as ugly as before.

Let’s do something about that:

# Main code
NewExcelObj
AddSheets
[array]$headerlist = ”Header1”,”Header2”,”Header3”
AddHeaders $headerlist
CloseExcel
Function AddHeaders ($AHhl)
     {
          $col = 1
          Foreach ($t in $AHhl)
               {
                    $xlsheet1.Cells.Item(1,$col) = $t
                    $xlsheet1.Cells.Item(1,$col).Font.Bold = $True
                    $xlsheet1.Cells.Item(1,$col).Interior.ColorIndex = 15
                    $col++
               }
     }

post4

We are doing A lot of things here at once. First, just before the call of the function AddHeaders we are defining an array that contains our header values. We then pass that variable to AddHeaders – notice the line for the call of AddHeaders now is “AddHeaders $headerlist” and our Function header is “Function AddHeaders ($AHhl)” where $AHhl is contains the passed array values of $headerlist. We now have a function-local variable called $col to keep track of what column we are in. We go into a standard Foreach loop where we step through each member of $AHhl, populate the header column, and move on with incrementing $col ($col++). Between the two lines populating and incrementing are two lines that contain formatting instructions for the cells we just touched. The first sets the font in the cell to bold, the second sets a fill colour in the cell of a light grey.

That makes our headers stand out:

There’s still one more thing we should do to our sheet – autofit the columns. There is nothing more annoying to people looking at a spreadsheet than having to change the column width. We can accomplish this with just two lines of code added to our CloseExcel function.

Function CloseExcel
     {
         $UsedRange = $xlsheet1.UsedRange
         [void] $UsedRange.EntireColumn.Autofit()
         $xlbook.SaveAs("REPORT.XLSX")
         $xl.quit() | Out-Null
     }

We define a variable in the scope of the function called $UsedRange and assign the used range property of the sheet to it. After that we use the Autofit() method on the property EntireColumn forcing all of the used columns to be autofit to the data within the column. Finally we perform our previously defined quit statements, saving and closing the workbook.