Thursday, November 16, 2017

Reset Orphaned AdminSDHolder objects

I recently discovered some domain objects which had once been a member of a protected group.  In case you didn't know, Active Directory users have a flag called "AdminCount" which is set to 1 when the user is added to a protected group.  In Windows 2008 ADDS and up, these groups are:

Account Operators
Administrators
Backup Operators
Domain Admins
Domain Controllers
Enterprise Admins
Print Operators
Read-only Domain Controllers
Replicator
Schema Admins
Server Operators

In addition, there are two accounts which are also protected:

Administrator
Krbtgt

You can read more about adminSDholder on technet, but in summary any object with this flag has its ACL overwritten with a copy from the SDholder object located in the System container.  Inheritance is also blocked on these accounts so that their ACLs do not get overwritten by another method.
And the article took me to a support article detailing behavior you might expect if you have this happening to accounts which once were members of a protected group and a vbscript on how to clean up that.


The real solution is probably to delete these accounts, but that was not feasible in this situation so I set out to recreate that vbscript's functionality in PS.  Using the AD cmdlets, it becomes so much more elegant...



$adminusers = Get-ADuser -LDAPFilter "(&(objectcategory=person)(objectClass=user)(admincount=1))" | where {$_.name -ne "krbtgt" -and $_.name -ne "Administrator"} 

ForEach($user in $adminusers) {

     Set-ADuser -Identity $user -replace @{Admincount = 0}

     $dACL = Get-ACL ($user.DistinguishedName) 

     $dACL.SetAccessRuleProtection($false,$false)

     Set-ACL -Path $user.DistinguishedName -AclObject $dACL

    }

Wow!  That's much better. 



Don't do this in your production environment unless you really know the full consequences. 

Friday, July 28, 2017

Monitor an Exchange Mailbox with Powershell...well, mostly EWS and .NET

Recently, I had a use case to start a workflow based on an email sent to an inbox.  After a brief search I determined that there's not really a way to do this with PowerShell.  Sometimes our reach exceeds PowerShell's grasp and for those times it is fortunate that PowerShell can play so well with .NET.

To accomplish this task, it is necessary to install the Exchange Web Service API.  You can find it here:
Download Microsoft Exchange Web Services Managed API 2.2
You can find the full documentation here:
Start using web services in Exchange

The first step is to download and install the EWS API.  Once that is done, you'll need to load it to gain its powers.


# Load the EWS Managed API

$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"

[void][Reflection.Assembly]::LoadFile($dllpath)

Once you've got it loaded, we can create an object in PowerShell and start manipulating it.  If you are working with .NET objects, then you need to head to the MSDN and do some research on the class you are trying to manipulate.  In this case, we are going to use the Exchange Service class.  We are looking at the ExchangeService constructor and we are going to specify the version of EWS we need when calling the class (2013 in this case).

Reading the documentation on the ExchangeService class, we can see that we have options for authentication.  In my case, I needed to pass credentials through to this, rather than use the credentials of the user running the script.  For example purposes, I'm just going to provide a PSCredential object by calling the Get-Credential cmdlet and inputting credentials with access to the account I'm using:

Note that looking at the documentation for the ExchangeServiceBase.Credentials property we need to convert the PSCredential object to a Network Credential.

Once we've got authentication, we need an endpoint to auth to.  The EWS autodiscover endpoint can help us and there's a handy method on the ExchangeService class that we can use to connect to it with the mailboxname.


$exchangeService  = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)

# Set Credentials in Exchange WebService Object

$ExchangeAdminCreds = Get-Credential

$exchangeService.Credentials = $ExchangeAdminCreds.GetNetworkCredential()

# Autodiscover to determine EWS URL

$mailboxname = "YourMailbox@yourexchange.com"

$exchangeService.AutodiscoverUrl($mailboxName)

For my needs, I wanted to monitor the inbox of this folder.  I discovered that to connect to the inbox, we needed to go up a level to the Microsoft.Exchange.WebServices.Data namespace and find the WellKnownFolderName enumeration, and then use this with the mailbox name to get the ID with Microsoft.Exchange.WebServices.Data.FolderId constructor.  Once you've got that, you can bind to the folder.  Sometimes, you'll need to work backwards to determine how these work together (start at the action you need to perform and read what it requires).


# Load type for WellKnownFolderName

$inboxFolderName = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox

# Identify FolderID from target mailbox 

$InboxFolderID =  New-Object -TypeName Microsoft.Exchange.WebServices.Data.FolderId -argumentlist $inboxFolderName, $mailboxName

# Bind to the Inbox folder of the target mailbox

$inboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeService,$inboxFolderID)

You're thinking, great - now I've got my binding to the folder and I'm ready to start getting emails.  Nope, we've still got some more work to do.  It might be a good idea, depending on your needs to not get a billion hits.  You can limit this with an itemview.  Additionally, we'll need to set up a searchfilter to find the results we need.  For this example, I need to limit by multiple criteria.  This requires a SearchFilterCollection.  To create that, we look at its constructor and work backwards for what we need (datetimerecieved and from for example).


# Optional: reduce the query overhead by viewing the inbox 10 items at a time

$itemView = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList 10

# Load Types for creating search filter. 

$dateTimeItem = [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived

$from = [Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::From

$searchbefore = (Get-Date).AddMinutes(-1)

$searchafter = (Get-Date).AddDays(-2)

$emailfrom = "someaddress@somewhere.com"

$searchFilter = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThanOrEqualTo -ArgumentList $dateTimeItem,$searchafter

$searchFilter1 = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo -ArgumentList $dateTimeItem,$searchbefore

$searchFilter2 = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo -ArgumentList $from,$emailfrom

$SearchFilterCollection = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection -ArgumentList "And",$searchFilter

$SearchFilterCollection.Add($searchFilter1)

$SearchFilterCollection.Add($searchFilter2)

Now, we can finally put this all together using the FindItems method of the ExchangeService class.  As I mentioned earlier, the way to navigate this is to find what you need to do and work backwards.
The $FoundItems variable contains an array of emails which should match your search criteria.  I'll leave it to you to manipulate those and explore MSDN. 

# Use FindItems method with arguments FolderID,SearchFilter,ItemView

$FoundItems = $exchangeService.FindItems($inboxFolder.Id,$searchFilterCollection,$itemView)