Monday, April 23, 2018

How to Find and Purge Email in Exchange Online


Run a content search under Security and Compliance

After the search is complete run this command via PowerShell:

New-ComplianceSearchAction -SearchName “SearchName” -Purge -PurgeType SoftDelete 

Saturday, January 6, 2018

Okta Automation - Reactivating Deprovisioned Accounts

Our Okta environment (as of this writing) is almost exclusively populated via the integration with our Active Directory domains, using the Okta AD Agents. Very few Okta-mastered accounts. Basically, users are hired in Workday, which flows down to Active Directory via a TIBCO integration, and the Okta AD Agent then imports the accounts into Okta.

When a user is terminated in Workday, the integration sets the expiration date on the user's AD account, and when that change imports into Okta, the user's Okta account goes into a "Deprovisioned" status. Within a few days, the user's account is deleted from Active Directory, per our security policies.

Recently, Human Resources asked us to come up with a way to allow former associates (they now use the term, "alumni") to access Workday for up to 2 years after their termination, primarily so they can access their W-2 tax forms. Reasonable request, although they didn't give us much time to come up with an automated solution before this year's crop of seasonal associates were let go. Remember, the users' AD accounts no longer exist (and our security policies demand that those accounts get deleted shortly after termination), and their Okta accounts have been deactivated (actually, I was deleting the deactivated accounts manually, semi-regularly).

At first, I used the okta.core.automation powershell module to build a scripted solution, and it worked pretty well. Unfortunately, the module has some significant limitations, and less than 2 weeks before go-live, Okta Support informed me that the module was no longer being developed or supported. Well, that just wouldn't do. I didn't want to provide a solution that might break without warning, especially if so many users would be affected, so I started looking for another solution.

Third party modules are too fickle, so I wanted to find something that followed the Okta guidelines and examples more closely. In my research, I noticed that their developer docs included examples using the 'curl' command (it's a REST API, and most of Okta's docs seem to be Linux-oriented). While Windows doesn't have a native curl command, powershell does alias 'curl' to the Invoke-WebRequest command, which essentially performs the same function as curl - you build and submit http/https requests to a REST API. I'd never used Invoke-WebRequest before, so I had to do some web searches and see how other folks were using it, and eventually came up with commands that retrieved the information I needed. Here's the resulting code.

$api_token = "[REMOVED]"

$allusers = @()

$webrequest = Invoke-WebRequest -Headers @{"Authorization" = "SSWS $api_token"} -Method GET -Uri "https://mycompany.okta.com/api/v1/users?filter=status%20eq%20%22DEPROVISIONED%22"

$json = $webrequest | ConvertFrom-Json

$allusers += $json

foreach ($usr in $allusers) {

if ($usr.profile.employeenumber -match '^\d\d\d\d\d\d$' -OR $usr.profile.employeenumber -match '^C\d\d\d\d\d\d$') {

write-output "Reactivate account: $($usr.profile.employeenumber) $($usr.profile.displayname) $($usr.id)"

Invoke-WebRequest -Headers @{"Authorization" = "SSWS $api_token"} -Method POST -Uri "https://mycompany.okta.com/api/v1/users/$($usr.id)/lifecycle/activate"

Invoke-WebRequest -Headers @{"Authorization" = "SSWS $api_token"} -Method PUT -Uri "https://mycompany.okta.com/api/v1/groups/00gdnc2yo5OILSKLK2p6/users/$($usr.id)" }
}

Line 1: For this to work, you need an API token from Okta.
Line 2: Create an empty array to hold our query results
Line 3: Query Okta for all accounts currently in "DEPROVISIONED" status
Line 4: Convert the JSON response to a powershell array
Line 5: Add the converted data to the array we initialized earlier
Line 6: Start looping through the results
Line 7: Workday-mastered users have employeeNumbers in a specific format
Line 8: Output the detail for each user
Line 9: Reactivate the account that matches the criteria in Line 7
Line 10: Add the reactivated account to a special Okta-mastered group that is used to assign the user to the Workday application

We don't actually need the $allusers array at the moment, but I do intend to expand on this code to include the possibility of more than one page of results. By default, Okta will only return 1000 results to a query, and if there are more, the code will need to examine the results and retrieve the next page, and in that situation, we'll need to add the additional data to $allusers, but for right now, it's just a placeholder. I'll go into how to handle paged results in a future article.

That's it. I hope you find that useful, or at least informative.

Cool factor - this solution uses nothing but native powershell code, so it will work not only on Windows, but also on Macs or Linux machines with the open source powershell code from Microsoft installed. Verified on my Macbook Pro this morning. I have to admit that I giggled just a little bit when the code ran without modification. It's just so cool!

Post-2017 Update

It's been a busy year, and it shows no sign of slowing down in 2018. In addition to our never-ending migration from Exchange 2010 On-Premises (EXP) to Exchange Online (EXO) and Lync 2010 to Skype for Business Online, things have been very busy on the Okta front as well. In addition to a rather bizarre implementation of multifactor authentication in our Okta environment, a more-or-less last minute project to provide post-employment access to Workday via Okta for 90,000+ former associates has been keeping me busy.

More to come...

Wednesday, May 4, 2016

You Do Not Have Sufficient Privileges To Delete a User

You may run into this from time to time. You're trying to delete a user account from Active Directory, and a dialog box pops up that says, "You do not have sufficient privileges to delete...". The annoying thing is that you DO have sufficient privileges to perform this action. You're the domain admin, possibly even the enterprise admin. What's going on here?

Well, a quick google of that phrase returns more than a few matches, almost all saying the same thing - the object is protected from accidental deletion, and to turn this off, you have to go to the Object tab and uncheck the box that is labeled, "Protect object from accidental deletion". Simple enough, eh?

But wait. On this particular object, that box isn't checked, yet you still can't delete the account. WTH?

I've encountered this problem more than once, yet it's rare, and it takes me a few minutes to remember why it's doing this, and how to fix it.

The account in question belongs to a former admin, or possibly an Account Operator. The problem is that permissions inheritance is turned off on this account. To understand why, take a look at this article. In a nutshell, "adminCount" is greater than 0 and permissions inheritance is turned off, including the permission you need to delete the account.

The fix is simple. Go to the account's Security tab, click the Advanced button, and then uncheck the box next to "Include inheritable permissions from this object's parent". Now click Apply or OK, close the properties dialog, and try to delete the object again. Voila! The account can now be deleted.

Just a quick side note. Don't wait too long to delete the account, or else permissions inheritance will be automatically disabled again, and you'll have to go through all of this again.

Quick note (2) - this permissions inheritance on new admin accounts will also prevent these users from adding an ActiveSync device to their Exchange mailbox. But only the first one. The problem is that, with permissions inheritance turned off, the user lacks the right to create the "subfolder" on their account where ActiveSync devices are stored. Again, the solution is to re-enable permissions inheritance on the account, then have the user retry adding their ActiveSync device, but do it before inheritance is re-enabled automatically.

Friday, January 15, 2016

Azure AD Connect and Deleting a Large Number of On-Premises Users

So, here's a handy URL to bookmark:

https://azure.microsoft.com/en-us/documentation/articles/active-directory-aadconnectsync-feature-prevent-accidental-deletes/

This is the page that explains how to get Azure AD Connect moving again if it gets stuck due to a large number of on-premises account deletions.

Being in the retail sector, our company hires, and subsequently terminates, a large number of seasonal associates, and this year was no different. This morning, I found several messages in my inbox from the "MSOnlineServicesTeam", stating that...

"the Identity synchronization service detected that the number of deletions exceeded the configured deletion threshold for [company name]. A total of 16402 objects were sent for deletion in this Identity synchronization run. This met or exceeded the configured deletion threshold value of 500 objects.

We need you to provide confirmation that these deletions should be processed before we will proceed."

The accompanying link leads to a site with instructions for disabling the limit for DirSync, but also has the above link for similar instructions for Azure AD Connect users.

First, verify that your pending deletes are not accidental. I checked with our HR department to confirm that they terminated those 16000+ associates yesterday, and then ran the following powershell command from the Azure AD Connect server:

Disable-ADSyncExportDeletionThreshold

Next, either wait for the scheduled synchronization task to run, or kick it off manually. Either way, it's going to take much longer than normal to push all those deletes up to Office 365.

Finally, be sure to re-enable the delete threshold, just to be safe.

Enable-ADSyncExportDeletionThreshold

Thanks to Andreas Kjellman and his team for all the great work they've done on Azure AD Connect.

Wednesday, November 11, 2015

Modifying License Assignments in Office 365

Automating the assigning of licenses in Office 365 isn't terribly hard - unless you have multiple subscription packages... and multiple Active Directory domains... and you're in hybrid mode with Exchange on-premises... and you only want to assign certain features of those subscriptions, and then only to certain users. This is one of those stories. Our company originally purchased an E3 subscription for our corporate "knowledge workers", and a K2 subscription for our store associates. Last year we added a K1 subscription for our seasonal associates, and I recently learned that the K2 subscription had been discontinued, so we had to increase our K1 subscription to cover all store associates. Due to this, I recently had to convert a large group of users from the discontinued K2 subscription to the K1 subscription, and only enable them for the sharepoint online feature.

After connecting to our tenant...

connect-msolservice

... I configured the K1 license options to exclude the Exchange Online feature...

$StoreK1License = New-MsolLicenseOptions -AccountSkuId "tenantname:DESKLESSPACK" -DisabledPlans EXCHANGE_S_DESKLESS

Next, I ran a query against our on-premises Active Directory, to build a list of user accounts that needed to be changed...

$k2users = get-aduser -filter {(extensionattribute11 -notlike "Regular") -and ((company -eq "Company1") -or (company -eq "Company2"))} -Server mydomain.local -SearchBase "ou=store employees,ou=stores,dc=mydomain,dc=local"

Now that we have our array of user accounts, we'll use the UserPrincipalname property to locate those users in Office 365. First, we check to see if they do indeed have the K2 license assigned...

foreach ($k2user in $k2users) {
if ((Get-MsolUser -UserPrincipalName $k2user.userprincipalname).licenses[0].accountsku.skupartnumber -eq "DESKLESSWOFFPACK") {

If the call to Get-MsolUser returns true, we proceed to (1) make sure the Usage Location is set to "US", then (2) we remove the K2 license and assign the new K1 license, with our previously defined options, all in one fell swoop...

write-host "Converting $($k2user.userprincipalname) from K2 to K1..." -foreground yellow
set-msoluser -userprincipalname $k2user.userprincipalname -usagelocation "US"
Set-MsolUserLicense -UserPrincipalName $k2user.userprincipalname -RemoveLicenses "tenantname:DESKLESSWOFFPACK" -AddLicenses "tenantname:DESKLESSPACK" -LicenseOptions $StoreK1License

And if the call to Get-MsolUser returned false, the user did not have a K2 license, so we just send a message to the screen and move on to the next user in the list.

} else {
write-host "K2 not found for $($k2user.userprincipalname)..." -foreground darkgray
}
}

I try not to use write-host commands in my regular scripting, but this was a quick and dirty task, and if I had left them out, since the Set-MsolUser and Set-MsolUserLicense commands produce no output (unless they encounter an error), write-host is in there just to provide evidence of progress. My AD query returned over 25000 user accounts, and it takes quite a while to process all those license changes, so the write-host output was good enough for the task at hand.

Here's the whole script:

connect-msolservice

$StoreK1License = New-MsolLicenseOptions -AccountSkuId "tenantname:DESKLESSPACK" -DisabledPlans EXCHANGE_S_DESKLESS

$k2users = get-aduser -filter {(extensionattribute11 -notlike "Regular") -and ((company -eq "Company1") -or (company -eq "Company2"))} -Server mydomain.local -SearchBase "ou=store employees,dc=mydomain,dc=local"


foreach ($k2user in $k2users) {
if ((Get-MsolUser -UserPrincipalName $k2user.userprincipalname).licenses[0].accountsku.skupartnumber -eq "DESKLESSWOFFPACK") {
write-host "Converting $($k2user.userprincipalname) from K2 to K1..." -foreground yellow
set-msoluser -userprincipalname $k2user.userprincipalname -usagelocation "US"
Set-MsolUserLicense -UserPrincipalName $k2user.userprincipalname -RemoveLicenses "tenantname:DESKLESSWOFFPACK" -AddLicenses "tenantname:DESKLESSPACK" -LicenseOptions $StoreK1License
} else {
write-host "K2 not found for $($k2user.userprincipalname)..." -foreground darkgray
}
}


Wednesday, October 28, 2015

Problems Deleting a 2007 Public Folder Database

It's finally time to decommission our Exchange 2007 environment, and after several days of messing around with public folder replicas, I finally got them all moved off and was ready to delete the public folder database. Denied!

The error was something like "object is read only because it was created by a newer version of Exchange." Balls.

Hopped on an Exchange 2010 server, opened an EMS console and ran Get-PublicFolderDatabase. Hmm. Just shows me the 2010 databases. There must be a way.

"Help Get-PublicFolderDatabase" revealed a previously unknown (to me) switch named "IncludePreExchange2010". A-HA!

The solution was to select the 2007 database, and then pipe the object to Remove-PublicFolderDatabase.

Get-PublicFolderDatabase 'DBNAME' -IncludePreExchange2010 | Remove-PublicFolderDatabase

Worked like a charm. All that's left to do is uninstall Exchange 2007 and it'll finally be history.