Categories
Uncategorized

Find the top-level manager for any Active Directory user

I was recently asked to report on who the vice president is for a list of users. The PowerShell function below does that for the users who have a manager designated on their Active Directory account. It excludes the President and Provost, so essentially reports to the level just below their positions.


function Get-TopLevel
{
param ($userID)
$subject = Get-ADUser $userID -Properties manager, displayName

if ($subject.manager -eq $null) {
Write-Host “No manager data available.”
}
else {
$userObj = $subject
while `
(($userObj.manager -ne $null) -and `
($userObj.manager -ne ‘CN=President,OU=Employee,OU=People,DC=university,DC=edu’) -and `
($userObj.manager -ne ‘CN=Provost,OU=Employee,OU=People,DC=university,DC=edu’))
{
$userObj = Get-ADUser $userObj.manager -Properties manager, displayName
# $userObj.displayName
}
Write-Host $userObj.displayName
}
}

Categories
PowerShell

Compare the Exchange Alias to the Primary SMTP Address

Here’s a data quality report for an Exchange environment. This command looks for Alias values that do not match the “username” portion of the PrimarySMTPAddress values. Any discrepancies are returned.

Get-Mailbox -ResultSize unlimited | where {$_.PrimarySmtpAddress.ToString() -notlike $_.Alias.ToString()+"@*"} | ft name, Alias, PrimarySmtpAddress

Categories
PowerShell

Search Active Directory for a list of email addresses

I’m occasionally asked to provide a list of account names based on a list of email addresses. This is pretty straight foward when the list of addresses contains only primary email addresses.

Get-Content C:\scripts\users.txt | %{Get-ADUser -Filter {mail -like $_} -Properties *} | ft name, displayName, eduPersonPrimaryAffiliation, PasswordLastSet -AutoSize

However, it’s very important to point out that the previous command will only match on the primary email address. Most of the time our list of address does not contain only primary email addresses – it may contain one of several secondary addresses the user has, which Active Directory refers to as proxyAddresses.

To make sure we search through all accounts for all their email address, we have to modify the input file and also modify our command a bit.

  1. Prefix the string smtp: to be beginning of each address in your file.
    1. For example, if you have address address@domain.edu in your file, modify that line to smtp:address@domain.edu
  2. Run the following PowerShell command – it’s all one line.
    1. Get-Content C:\scripts\users.txt | %{Get-ADUser -Filter {proxyAddresses -like $_} -Properties *} | ft name, displayName, mail, eduPersonPrimaryAffiliation, PasswordLastSet -Autosize

That output will include the primary email address for each account, so you can validate the results against your input file.

Categories
Office 365 PowerShell

Connecting to Office 365 with automation

In order to automate management activities for Office 365, it’s imperative to connect to the remote environments without human interaction. Typically this type of automated job will be run with Task Scheduler. However, due to differences between Windows authentication and Office 365 authentication, this is not quite as straightforward as running a Task Scheduler job with a service account in a pure Windows Server environment.

Fortunately there is a method of creating a file-based credential that securely associates an Office 365 user account with a local or Active Directory user account. We can then provide that credential file to the Office 365 connection string in our scripts. This allows us to schedule our scripts in Task Scheduler, run them as the local or Active Directory user account, and gain access to Office 365 resources.

  1. First, it’s important to logon to the server where the Task Scheduler will run, and to logon there as the user account that will be running the Task Scheduler job (i.e., domain\userA). This can be either a local user account or an Active Directory user account.
  2. Now we create the credential file using the Export-Clixml cmdlet. We must specify the Office 365 user account we plan to use in our scripts (i.e., cloudUser@tenant.onmicrosoft.com), and we’ll be prompted for the password on that cloud account when we execute the following command. An XML credential file will be produced in the path we specify in the Export-Clixml cmdlet.
    Get-Credential "cloudUser@tenant.onmicrosoft.com" | Export-Clixml C:\credentials\office365_credential.xml

    1. Note that the credential file we create here is tied to this account on this machine, and can only be used on this machine.
  3. Now that the credential file has been created, we can log out of the server and log back in with our standard administrative account if desired.

Now that we have the credential file, we can use it in our scripts to gain access to any Office 365 resource where this Office 365 user account (i.e., cloudUser@tenant.onmicrosoft.com) has permissions as long as we run those scripts as the associated user account (i.e., domain\userA). Keep in mind that the credential file will be unusable if it is copied to another computer, or even if it is used by any other user account on the computer where it was created!

Here is a sample connection string to connect to Exchange Online using our credential file as long as it’s run by the domain\userA account.

$credential = Import-Clixml C:\credentials\office365_credential.xml
$Session = New-PSSession `
-ConfigurationName Microsoft.Exchange `
-ConnectionUri https://outlook.office365.com/PowerShell-LiveID?PSVersion=4.0 `
-Credential $credential `
-Authentication Basic `
-AllowRedirection
Import-PSSession $Session

Here is a sample connection string to connect to Microsoft Online using our credential file.

Import-Module MSOnline
$credential = Import-Clixml C:\credentials\office365_credential.xml
Connect-MsolService –Credential $credential

Categories
PowerShell

compare two lists with PowerShell

I am asked occasionally to compare a list of employee ID numbers to find the differences or the matches. Notepad++ does not do a great job of this, so I put together a quick PowerShell solution.

$dupes = @()
[System.Collections.ArrayList]$arrA = Get-Content U:\listA.txt
[System.Collections.ArrayList]$arrB = Get-Content U:\listB.txt
foreach ($itemA in $arrA) {
if ($arrB -match $itemA) {
$arrB.Remove($itemA)
$dupes += $itemA
}
}

Now $arrB contains only items from listB.txt that do not also appear on listA.txt. Also $dupes contains the items that exist in both files.

Categories
Office 365

Office 365: removing Litigation Hold mailboxes in an Exchange Hybrid environment

In our hybrid Exchange 2010 / Exchange Online environment, we occasionally need to place an Exchange 2010 mailbox on Litigation Hold. In some cases, that user’s mailbox will need to be removed but the Active Directory account will need to be retained. Exchange 2010 will not allow a mailbox on Litigation Hold to be removed, so our practice has been to simply export the mailbox to PST for retention, manually remove the Litigation Hold, and then remove the mailbox. However, we’ve learned that Exchange Online requires a slight change to that procedure.

Exchange Online was reporting an error regarding a few such users.
Exchange: An unknown error has occurred. Refer to correlation ID:

Referencing this article to help determine the problem, I ran some code against MSOL to look at a more detailed error report.
http://support2.microsoft.com/kb/2741233

$errors = (Get-MsolUser –UserPrincipalName user@domain.edu).Errors
$errors | foreach-object {"`nService: " + $_.ErrorDetail.Name.split("/")[0]; "Error Message: " + $_.ErrorDetail.ObjectErrors.ErrorRecord.ErrorDescription}

The output provided the details need to understand the problem.

Service: exchange
Error Message: Exchange can't disable the mail user "NAMPRXXXXXX.prod.outlook.com/Microsoft Exchange Hosted Organizations/tenant.onmicrosoft.com/user" because it is on litigation hold.

First I tried to simply remove the MsolUser using this command.
Remove-MailUser –Identity name@domain.com –IgnoreLegalHold

However, that returned an error.

The following error occurred during validation in agent 'Windows LiveId Agent': 'Unable to perform the save operation. 'user' is not within a valid server write scope.'

After engaging Microsoft on the problem, we determined there are two options to address the error:

  1. If the MSOL account is not actually required in Azure Active Directory (AAD), we can simply delete it and purge it from the AAD recycle bin. At the next DirSync cycle, a new MsolUser will be created and the error will be resolved. (See the important NOTE below.)
  2. An Exchange Online license could be assigned temporarily to the MsolUser to create a new Exchange Online mailbox. After allowing time for the mailbox to be created plus additional time for a DirSync cycle, remove the Exchange Online license again, and the mailbox will be deleted. This should allow the backend processing to occur and resolve the error.

In most cases, Option 1 is probably most palatable. I issued these two commands, and after the regular DirSync scheduled sync, the error has been resolved. Of course you can add the -Force parameter to quickly execute the commands without having to confirm.

NOTE: If the AAD account is removed, this will also remove the user’s access to other Office 365 data such as OneDrive for Business.

Remove-MsolUser -UserPrincipalName user@domain.edu
Remove-MsolUser -UserPrincipalName user@domain.edu -RemoveFromRecycleBin

In summary, the way to avoid the problem is to remove the Litigation Hold from the Exchange 2010 mailbox, then wait for a DirSync cycle, and then remove the Exchange 2010 mailbox. If both actions are taken quickly together and an error is reported in the Office 365 Admin Center, just purge the AAD account as described above to resolve the error.

Categories
Office 365 PowerShell

Rotate images in ADFS 3.0

ADFS 3.0 is otherwise known as ADFS 2012 R2 since it is available only on Server 2012 R2. As I gain some experience with it, one of the nice configuration options is the ability to use PowerShell to customize the sign-in page.

Among the customizations we’ve made is one to help keep our sign-in page from looking stale over time. I wrote this simple PowerShell script to rotate the large “illustration” image occasionally. It runs as a Scheduled Task, and pulls approved images randomly from a file system folder. The script also logs which image was in place at any given time in case that happens to be interesting to someone at some point.

cd X:\path\images
$RandomImage = Get-ChildItem | Get-Random | %{((Get-Item $_).VersionInfo).FileName}
(Get-Date -format G) + " $RandomImage" | Out-File X:\path\Logs\IllustrationRandomizer.log -Append
Set-AdfsWebTheme -TargetName Custom_Theme -Illustration @{path=$RandomImage}

Categories
PowerShell

PowerShell error with Get-ADUser user -Properties *

After upgrading some of our servers to Server 2012 R2, we’ve discovered a bug in the PowerShell 4.0 Get-ADUser cmdlet. When running the command Get-ADUser username -Properties *, the cmdlet returns the following error:

Get-ADUser : One or more properties are invalid.
Parameter name: msDS-AssignedAuthNPolicy
At line:1 char:1
+ Get-ADUser username -Properties *
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (username:ADUser) [Get-ADUser], ArgumentException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft.ActiveDirectory.Management.Comm
ands.GetADUser

Mike F Robbins researched the error in the following blog post, and determined that the issue occurs with PowerShell 4.0 run against Server 2008 R2 domain controllers. The issue is that two attributes, AuthenticationPolicy and AuthenticationPolicySilo, exist in a Server 2012 R2 Active Directory but do not exist in a Server 2008 R2 Active Directory. The Server 2012 R2 RSAT tools expect the attributes to exist in both environments, so an error is returned in the Server 2008 R2 Active Directory environment.

http://mikefrobbins.com/2013/11/07/windows-8-1-rsat-powershell-cmdlets-get-aduser-get-adcomputer-one-or-more-properties-are-invalid/

As Mike points out, a good workaround is to use PowerShell implicit remoting to process the commands on the domain controllers themselves. However, in the meantime I’m able to process the command from other downlevel OS machines, which is fine in my case for now.

I’ve submitted the case to Microsoft Premier who has confirmed the bug and escalated to the Platforms Team. I’ll post updates here as I get them.

Categories
PowerShell

SQL Server PowerShell Module (SQLPS)

SQL Server provides a Windows PowerShell module called sqlps that is used to import the SQL Server components into Windows PowerShell. The sqlps module loads two Windows PowerShell modules:

  • A SQL Server provider, which enables a simple navigation mechanism similar to file system paths. You can build paths similar to file system paths, where the drive is associated with a SQL Server management object model, and the nodes are based on the object model classes. You can then use familiar commands such as cd and dir to navigate the paths similar to the way you navigate folders in a command prompt window. You can use other commands, such as ren or del, to perform actions on the nodes in the path.
  • A set of cmdlets, which are commands used in Windows PowerShell scripts to specify a SQL Server action. The SQL Server cmdlets support actions such as running a sqlcmd script containing Transact-SQL or XQuery statements.

More details here: http://msdn.microsoft.com/en-us/library/hh245198.aspx

Although the SQLPS module is installed along with SQL Server, you do not have to install SQL Server to obtain the module. You simply need to install three stand-alone packages from the Microsoft® SQL Server® 2012 Feature Pack, available here:
http://www.microsoft.com/en-us/download/details.aspx?id=29065

Install the following packages in this order:

  1. Microsoft® System CLR Types for Microsoft® SQL Server® 2012 (SQLSysClrTypes.msi)
  2. Microsoft® SQL Server® 2012 Shared Management Objects (SharedManagementObjects.msi)
  3. Microsoft® Windows PowerShell Extensions for Microsoft® SQL Server® 2012 (PowerShellTools.msi)

Be sure to select the appropriate package platform for each, either x86 or x64.

To load the sqlps module in Windows PowerShell:

Import-Module sqlps

(You can include the -DisableNameChecking parameter if you’re concerned about suppressing the Encode-Sqlname and Decode-Sqlname warning.)

Thanks to Max Trinidad for his article explaining this information. http://www.maxtblog.com/2012/09/create-powershell-smo-scripts-without-installing-sql-server/

Categories
PowerShell

ActiveDirectory module and UAC

I have noticed a few odd behaviors with PowerShell’s ActiveDirectory module, one regarding the msDS-UserPasswordExpiryTimeComputed attribute of the Get-ADUser cmdlet, and another regarding the New-ADServiceAccount cmdlet.

On a brand new Server 2008 R2 domain, the following command returns values for only a small percentage of the accounts in the domain but the vast majority of accounts return with no value.

Get-ADUser -filter * -Properties “msDS-UserPasswordExpiryTimeComputed” | ft name, “msDS-UserPasswordExpiryTimeComputed” –AutoSize

Also on this domain, issuing the following command often results in an “Access is denied” error, but not always. In fact, typically if I receive the error and then leave my RDP session to the computer open for 30 to 60 minutes and try command again (hit the Up Arrow key on the keyboard), the command runs without error.

New-ADServiceAccount svc_test

This seems to indicate a general problem with the ActiveDirectory PowerShell module.

The following article pointed me in the right direction for a workaround.
http://social.technet.microsoft.com/Forums/en-US/windowsserver2008r2general/thread/5947c0eb-9549-4161-afa1-edb451fc742f

The problem appears to be a bug in the way the ActiveDirectory module behaves when User Account Control (UAC) is enabled. The workaround is to disable UAC, which requires a reboot. When UAC is left enabled, even when PowerShell is run with elevated permissions, the problem still occurs.

I’ve confirmed the experiences above both on domain controllers and on member servers, and in two different Active Directory forests.