Category Archives: PowerShell

Working with Azure AD Extension Attributes with Azure AD PowerShell v2

In a recent blog post, https://gotoguy.blog/2017/02/17/assign-ems-license-with-azure-ad-v2-powershell-and-dynamic-groups/, I wrote about how to use extension attributes in local Active Directory and Azure AD, for the purpose of using these extension attributes for determine membership i Azure AD Dynamic Groups.

In the process of investigating my Azure AD users (synchronized and cloud based),  I wanted to see how I could use Azure AD v2 PowerShell CmdLets for querying and updating these extension attributes. This blog post is a summary of tips and commands, and also some curious things I found. There is a link to a Gist with all the PowerShell Commands at the end of the blog post if you prefer to skip to that.

Lets start by looking into one user:

image

For my example user I have the following output:

image

In the above linked blog post, I wrote about using the msDS_cloudExtensionAttribute1 and 2 for assigning licenses, so I see those values and more.

I can also serialize the user object to JSON by using $aadUser.ToJson(), which also will show me the value of the extension attributes:

image

I can look into and explore the user object with Get-Member:

image

From there I can see that that the Extension Property, which is of type System.Collections.Generic.Dictionary supports Get and Set. So lets look into how to update those extension attributes. This obviously only work on cloud homed users, as synchronized users must be updated in local Active Directory. This series of commands shows how to add extension attributes for cloud users:

image

The next thing I thought about, was how can I make a list of all users with their extension attributes? I ended up with the following, where you either can get all users or make a filtered collection, and from there loop through and read any extension attributes:

image

When I look into my extended users list object, I can list the users and values with extension values:

image

image

So to some curios things I found. As explained in the blog post https://gotoguy.blog/2017/02/17/assign-ems-license-with-azure-ad-v2-powershell-and-dynamic-groups/, if you are running a Hybrid Exchange organization you would probably use extensionAttribute1..15 instead of the msDS_cloudExtensionAttribute. In another Azure AD tenant I tested on that, but using the commands above I never could list out the extensionAttribute1..15 on my users. I never found a way to validate and check those values, but if I created a Dynamic Group using for example extensionAttribute1 or 2, members would be populated! So it was obvious that the value was there, I just can’t find a way to check it.

I even tested on Graph API, but did not find any extensionAttribute there either, only msDS_cloudExtensionAttribute. For example by querying:

https://graph.microsoft.com/v1.0/users/<userid or upn>?$select=extension_66868723f2984d3e8c18f0ebd134240f_msDS_cloudExtensionAttribute2

image

I can see my extension value.

However, if I try: https://graph.microsoft.com/v1.0/users/<userid or upn>?$select=extensionAttribute2, I cannot see the value even I know it’s there.

image

Strange thing, hopefully I will find out some more on this, and please comment if you have any ideas. I will also ask this from the Azure AD team.

Here is the gist with all the commands:


# Azure AD v2 PowerShell Module CmdLets for working with Extension Attribute Properties
# Connect to Azure AD with Global Administrator
Connect-AzureAD
# Get a User and Read Extension Properties
$aadUser = Get-AzureADUser ObjectId <youruser>
$aadUser | Select ExpandProperty ExtensionProperty
# Serialize User Object to JSON
$aadUser.ToJson()
# Explore Object Properties
$aadUser | Get-Member
# How to: Add Extension Properties
# PS! Can only write to Cloud homed users
$aadUser = Get-AzureADUser ObjectId <yourclouduser>@elven.onmicrosoft.com
$extensionProp = New-Object "System.Collections.Generic.Dictionary2[System.String,System.String]"
$extensionProp.Add('extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute1','ENTERPRISEPACK')
$extensionProp.Add('extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2','EMSPREMIUM')
Set-AzureADUser ObjectId $aadUser.ObjectId ExtensionProperty $extensionProp
# Check added Extension Properties
Get-AzureADUser ObjectId <yourclouduser>@elven.onmicrosoft.com | Select ExpandProperty ExtensionProperty
#region List all users with Extension Properties
$aadUsers = Get-AzureADUser | Select DisplayName, ObjectId
$aadUsersExt = @()
ForEach ($aadUser in $aadUsers) {
$user = Get-AzureADUser ObjectId $aadUser.ObjectId | Select ObjectId, DisplayName
$userDetail = Get-AzureADUser ObjectId $aadUser.ObjectId | Select ExpandProperty ExtensionProperty
        foreach ($key in $userDetail.Keys)
        {
            if($key -like "extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute1")
            {
                $ext1 = $userDetail."$key"
            }
            elseif($key -like "extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2")
            {
                $ext2 = $userDetail."$key"
            }
else { $ext1 = ""; $ext2 = "" }
        }
$obj = [pscustomobject]@{"DisplayName"=$user.DisplayName; "ObjectId"=$user.ObjectId; "Ext1"=$ext1; "Ext2"=$ext2}
$aadUsersExt += $obj
}
# List only users with values for extension attributes
$aadUsersExt | Where {$_.Ext1 -or $_.Ext2} | FT
#endregion
# List all users
$aadUsersExt
# Serialize users and extension attributes to JSON
$aadUsersExt | ConvertTo-Json

Assign EMS License with Azure AD v2 PowerShell and Dynamic Groups

While we are waiting for support for group based licensing in the Azure AD Portal I have created this Azure AD v2 PowerShell solution for assigning EMS (Enterprise Mobility + Security) license plans using Azure AD v2 PowerShell module and Dynamic Groups.

The PowerShell CmdLets used here requires the Azure AD v2 PowerShell Module, which you can read about how to install or update here: https://gist.github.com/skillriver/35fba9647fbfbe3e99718f0ad734b241

Source of Authority, Attributes, Sync and Dynamic Groups

In my scenario I want to use extension attributes to automatically calculate membership using Dynamic Groups in Azure AD. The members of these groups will be assigned the EMS licenses.

Most organizations will have an on-premises Active Directory synchronizing to Azure AD, so the source of authority is important for where I set the value of the extension attributes, as I want my Dynamic Groups to calculate membership for both On-premise and Cloud based users (I have some Cloud based admin account I want to license as well).

So, lets take a look at my local Active Directory environment. If you have Exchange installed in your organization, you will have extended the schema with extensionAttribute1..15.

But in my case, I never have installed any versions of Exchange in my current environment, and only used Exhange Online, so I don’t have those attributes. Instead I have msDS-cloudExtensionAttribute1..20.

So I decided on using the following attributes locally in AD:

image

I have previously used ENTERPRISEPACK (SkuPartNumber for Office 365 E3) for licensing Office 365 E3 plans. In this scenario I will use the msDS-cloudExtensionAttribute2 for either EMS (SkuPartNumber for EMS E3) or EMSPREMIUM (SkuPartNumber for EMS E5).

You can also use Active Directory PowerShell to set these values on-premises:

image

Note that if I had Exchange installed, I could just have used extensionAttribute1 and extensionAttribute2, and these would be automatically synchronized to Azure AD in an Exchange Hybrid deployment. However, in my case I need to manually specify the option for Directory extension attribute sync in Azure AD Connect:

image

And then selecting to synchronize those two selected attributes:

image

After these Directory extensions are configured and synchronized to Azure AD, I can check these attributes with the following AAD v2 command:

Get-AzureADUser –ObjectId <youruser> | Select -ExpandProperty ExtensionProperty

In my environment I will find these attributes:

image

Note that the msDS_cloudExtensionAttribute1..2 has now been created in Azure AD for me, and been prefixed with extension_<GUID>_, where the GUID represent the Tenant Schema Extension App:

image

So now I know that my on-premises users with values for msDS_cloudExtensionAttribute1..2 will be synchronized to the extension attributes in Azure AD. But what about users that are source from Cloud? There are no graphical way to set these extension attributes, so we will have to do that with Azure AD v2 PowerShell. In my example I have a Cloud admin account I want to set this attribute extension for (scripts are linked later in the blog):

image

With that, I now have configured the users I want with the extension attribute values, and are ready to create the Dynamic Groups.

Creating Dynamic Groups for Assigning EMS Licenses

Earlier in the blog post I mentioned that I wanted to use the msDS_cloudExtensionAttribute2 for assigning either EMS E3 or EMS E5 licenses. If I run the following command, I get my Subscriptions, here listed by SkuId an SkuPartNumber. EMSPREMIUM refers to EMS E5, while EMS refers to the original EMS which is now E3.

image

On that basis I will create 2 Dynamic Groups, one that looks for EMSPREMIUM and one that looks for EMS in the extension attribute. You can create Dynamic Groups in the new Azure AD Portal, or by running these PowerShell commands:

image

After a while memberships in these dynamic groups will be processed, and I can check members with the following commands:

image

In my environment I will have this returned, showing users with membership in the EMS E3 and EMS E5 group respectively:

image

Before I proceed I will save these memberships to objects variables:

image

Assigning the EMS licenses based on group membership

With users, attributes and dynamic groups membership prepared, I can run the actual PowerShell commands for assigning the licenses. I also want to make sure that any users previously assigned to another EMS license will be changed to reflect the new, so that they are not double licensed. Meaning, if a user already has an EMS E3 license, and the script adds EMS E5, I will remove the EMS E3 and vice versa.

The full script is linked below, but I will go through the main parts here first. First I will save the SkuId for the EMS subscriptions:

image

Then I will loop through the membership objects saved earlier:

image

Next, create License Object for adding and removing license:

image

Then create a AssignedLicenses object, adding the AssignedLicense object from above. In addition, I check if the user has an existing EMS license to be removed, and if so add that SkuId to RemoveLicenses. If there are no license to remove, I still need to specify an empty array for RemoveLicenses.

image

And then, update the user at the end of the loop:

image

After looping through the EMS E3 members, a similar loop through EMS E5 members:

image

So to summarize, with this script commands you can assign either EMS E3 or E5 licenses based on user membership in Dynamic Groups controlled by extension attributes. In a later blog post I will show how we can consistenly apply these licenses, stay tuned!

Link to the full script is below:


# PowerShell CmdLets for Assigning EMS Licenses with Azure AD v2 PowerShell Module
# Read blog post for details: https://gotoguy.blog/2017/02/17/assign-ems-license-with-azure-ad-v2-powershell-and-dynamic-groups/
# Connect to Azure AD with Global Administrator
Connect-AzureAD
# List Subscriptions
Get-AzureADSubscribedSku | Select SkuId, SkuPartNumber
# EMS E3 license Service Plans
$EMSlicense = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq 'EMS'}
# EMS E5 license Service Plans
$EMSpremiumlicense = Get-AzureADSubscribedSku | Where-Object {$_.SkuPartNumber -eq 'EMSPREMIUM'}
# Create a Dynamic Group for EMS E3 Users to be Licensed
New-AzureADMSGroup DisplayName "EMS E3 Licensed Users" Description "Dynamic group for EMS E3 Users" `
SecurityEnabled $true MailEnabled $false MailNickname "EMSE3Users" GroupTypes "DynamicMembership" `
MembershipRule "(user.extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2 -eq ""EMS"")" `
MembershipRuleProcessingState "On"
# Create a Dynamic Group for EMS E5 Users to be Licensed
New-AzureADMSGroup DisplayName "EMS E5 Licensed Users" Description "Dynamic group for EMS E5 Users" `
SecurityEnabled $true MailEnabled $false MailNickname "EMSE5Users" GroupTypes "DynamicMembership" `
MembershipRule "(user.extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2 -eq ""EMSPREMIUM"")" `
MembershipRuleProcessingState "On"
# Get Group and members
$EMSE3Group = Get-AzureADMSGroup SearchString "EMS E3 Licensed Users"
# Check if membership has been processed, wait and try again if not yet
Get-AzureADGroupMember ObjectId $EMSE3Group.Id
$EMSE5Group = Get-AzureADMSGroup SearchString "EMS E5 Licensed Users"
# Check if membership has been processed, wait and try again if not yet
Get-AzureADGroupMember ObjectId $EMSE5Group.Id
# Save members to object variable
$membersEMSE3 = Get-AzureADGroupMember ObjectId $EMSE3Group.Id
$membersEMSE5 = Get-AzureADGroupMember ObjectId $EMSE5Group.Id
#region EMS License Management for Dynamic Group Membership
# Get SkuId for EMS E5 (EMSPREMIUM) and EMS
$EmsE3SkuId = (Get-AzureADSubscribedSku | Where { $_.SkuPartNumber -eq 'EMS'}).SkuId
$EmsE5SkuId = (Get-AzureADSubscribedSku | Where { $_.SkuPartNumber -eq 'EMSPREMIUM'}).SkuId
# Loop through EMS E3 Members
ForEach ($member in $membersEMSE3) {
# Get the user
$User = Get-AzureADUser ObjectId $member.ObjectId
# Create a License Object for assigning the EMS E3 SkuId
$AddLicense = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$AddLicense.SkuId = $EmsE3SkuId
# Create a License Object for removing the EMS E5 SkuId
$RemoveLicense = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$RemoveLicense.SkuId = $EmsE5SkuId
# Create a Licenses Object for Adding and Removing the Licenses
$Licenses = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
$Licenses.AddLicenses = $AddLicense
# Check if the User has license to be removed
If ($user.AssignedLicenses | Where-Object {$_.SkuId -eq $EmsE5SkuId}) {
$Licenses.RemoveLicenses = $RemoveLicense.SkuId
}
Else { $Licenses.RemoveLicenses = @() }
# And lastly, update User license with added and removed licenses
Set-AzureADUserLicense ObjectId $User.ObjectId AssignedLicenses $Licenses
}
# Loop through EMS E5 Members
ForEach ($member in $membersEMSE5) {
# Get the user
$User = Get-AzureADUser ObjectId $member.ObjectId
# Create a License Object for assigning the EMS E5 SkuId
$AddLicense = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$AddLicense.SkuId = $EmsE5SkuId
# Create a License Object for removing the EMS E3 SkuId
$RemoveLicense = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$RemoveLicense.SkuId = $EmsE3SkuId
# Create a Licenses Object for Adding and Removing the Licenses
$Licenses = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
$Licenses.AddLicenses = $AddLicense
# Check if the User has license to be removed
If ($user.AssignedLicenses | Where-Object {$_.SkuId -eq $EmsE3SkuId}) {
$Licenses.RemoveLicenses = $RemoveLicense.SkuId
}
Else { $Licenses.RemoveLicenses = @() }
# And lastly, update User license with added and removed licenses
Set-AzureADUserLicense ObjectId $User.ObjectId AssignedLicenses $Licenses
}
#endregion

Link to script for managing and listing extension attribute properties for your users:


# Azure AD v2 PowerShell Module CmdLets for working with Extension Attribute Properties
# Connect to Azure AD with Global Administrator
Connect-AzureAD
# Get a User and Read Extension Properties
$aadUser = Get-AzureADUser ObjectId <youruser>
$aadUser | Select ExpandProperty ExtensionProperty
# Serialize User Object to JSON
$aadUser.ToJson()
# Explore Object Properties
$aadUser | Get-Member
# How to: Add Extension Properties
# PS! Can only write to Cloud homed users
$aadUser = Get-AzureADUser ObjectId <yourclouduser>@elven.onmicrosoft.com
$extensionProp = New-Object "System.Collections.Generic.Dictionary2[System.String,System.String]"
$extensionProp.Add('extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute1','ENTERPRISEPACK')
$extensionProp.Add('extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2','EMSPREMIUM')
Set-AzureADUser ObjectId $aadUser.ObjectId ExtensionProperty $extensionProp
# Check added Extension Properties
Get-AzureADUser ObjectId <yourclouduser>@elven.onmicrosoft.com | Select ExpandProperty ExtensionProperty
#region List all users with Extension Properties
$aadUsers = Get-AzureADUser | Select DisplayName, ObjectId
$aadUsersExt = @()
ForEach ($aadUser in $aadUsers) {
$user = Get-AzureADUser ObjectId $aadUser.ObjectId | Select ObjectId, DisplayName
$userDetail = Get-AzureADUser ObjectId $aadUser.ObjectId | Select ExpandProperty ExtensionProperty
        foreach ($key in $userDetail.Keys)
        {
            if($key -like "extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute1")
            {
                $ext1 = $userDetail."$key"
            }
            elseif($key -like "extension_<YourTenantSchemaExtensionAppId>_msDS_cloudExtensionAttribute2")
            {
                $ext2 = $userDetail."$key"
            }
else { $ext1 = ""; $ext2 = "" }
        }
$obj = [pscustomobject]@{"DisplayName"=$user.DisplayName; "ObjectId"=$user.ObjectId; "Ext1"=$ext1; "Ext2"=$ext2}
$aadUsersExt += $obj
}
# List only users with values for extension attributes
$aadUsersExt | Where {$_.Ext1 -or $_.Ext2} | FT
#endregion
# List all users
$aadUsersExt
# Serialize users and extension attributes to JSON
$aadUsersExt | ConvertTo-Json

Session Recap, PowerShell Scripts and Resources from session on Azure AD Management Skills at NICConf 2017

Last week at NICConf I presented two sessions on Management of Microsoft Azure AD, Application Publishing with Azure AD – the New Management Experience! and Take your Azure AD Management Skills to the Next Level with Azure AD Graph API and Powershell!

In the last session i presented demos and scripts with some technical details, so in this blog post I will link to those PowerShell scripts together with some explanations. See also my slides for the sessions published here: https://docs.com/jan-vidar-elven-1/7677/nicconf-2017, and the session recording might be available later which I will link to.

First i talked about the new Azure AD PowerShell v2 module and install info:


# Azure AD v2 PowerShell Quickstart module install
# Azure AD has a GA version: AzureAD and Preview version: AzureADPreview
# Check available versions installed
Get-Module AzureAD ListAvailable
Get-Module AzureADPreview ListAvailable
# Install from PowerShell Gallery
Install-Module AzureAD
Install-Module AzureADPreview
# Update new versions from PS Gallery
Update-Module AzureAD
Update-Module AzureADPreview
# Check and uninstall old versions
$Latest = Get-InstalledModule ("AzureADPreview")
Get-InstalledModule ("AzureADPreview") AllVersions | ? {$_.Version -ne $Latest.Version} | Uninstall-Module WhatIf
# Check Commands, see also list of commands at: ref. https://docs.microsoft.com/en-us/powershell/azuread/v2/azureactivedirectory
Get-Command Module AzureADPreview

Then connecting and exploring some objects and license info:


# Azure AD v2 PowerShell Quickstart Connect
# Connect with Credential Object
$AzureAdCred = Get-Credential
Connect-AzureAD Credential $AzureAdCred
# Connect with Modern Authentication
Connect-AzureAD
# Explore some objects
Get-AzureADUser
# Getting users by objectid, upn and searching
Get-AzureADUser ObjectId <objectid>
Get-AzureADUser ObjectId jan.vidar@elven.no
Get-AzureADUser SearchString "Jan Vidar"
# Explore deeper via object variable
$AADUser = Get-AzureADUser ObjectId jan.vidar@elven.no
$AADUser | Get-Member
$AADUser | FL
# Look at licenses and history for enable and disable
$AADUser.AssignedPlans
# Or
Get-AzureADUser ObjectId jan.vidar@elven.no | Select-Object ExpandProperty AssignedPlans
# More detail for individual licenses for plans
Get-AzureADUserLicenseDetail ObjectId $AADUser.ObjectId | Select-Object ExpandProperty ServicePlans
# Get your tenants subscriptions, and explore details
Get-AzureADSubscribedSku | FL
Get-AzureADSubscribedSku | Select-Object SkuPartNumber ExpandProperty PrepaidUnits
Get-AzureADSubscribedSku | Select-Object SkuPartNumber ExpandProperty ServicePlans
# Invalidate Users Refresh tokens
Revoke-AzureADUserAllRefreshToken ObjectId $AADUser.ObjectId

Then performing some Administration tasks including creating Dynamic Groups, setting user thumbnail photo, adding licenses and changing passwords:


# Create a Dynamic Group for my test users of Seinfeld characters
New-AzureADMSGroup DisplayName "Seinfeld Users" Description "Dynamic groups with all Seinfeld users" MailEnabled $false SecurityEnabled $true MailNickname "seinfeld" GroupTypes "DynamicMembership" MembershipRule "(user.department -eq ""Seinfeld"")" MembershipRuleProcessingState "Paused"
# Get Group and members
$AADGroup = Get-AzureADMSGroup SearchString "Seinfeld Users"
Get-AzureADGroupMember ObjectId $AADGroup.Id
# Set Membership Processing
$AADGroup | Set-AzureADMSGroup MembershipRuleProcessingState On
# Save members to object variable
$members = Get-AzureADGroupMember ObjectId $AADGroup.Id
# Set User Thumbnail Photo
# Note that setting Thumbnailphoto can only be set against cloud mastered objects, or else error message:
# Unable to update the specified properties for on-premises mastered Directory Sync objects or objects currently undergoing migration.
Set-AzureADUserThumbnailPhoto ObjectId <myuserupn or objectid> FilePath C:\_source\temp\jan.vidar@elven.no.jpg
# Get and View User Thumbnail Photo
Get-AzureADUserThumbnailPhoto ObjectId <myuserupn or objectid> view $true
#region License management for a collection of users
# For example assigning EMS E5 license plan
# Get SkuId for EMS E5 (EMS PREMIUM)
$EmsSkuId = (Get-AzureADSubscribedSku | Where { $_.SkuPartNumber -eq 'EMSPREMIUM'}).SkuId
ForEach ($member in $members) {
# Get the user
$User = Get-AzureADUser ObjectId $member.ObjectId
# Create a License Object for assigning the wanted SkuId
$License = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$License.SkuId = $EmsSkuId
# Create a Licenses Object for Adding the License
$Licenses = New-Object TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
$Licenses.AddLicenses = $License
# If I wanted to remove licenses I would use .RemoveLicenses instead
# And lastly, update User license with added (or removed) licenses
Set-AzureADUserLicense ObjectId $User.ObjectId AssignedLicenses $Licenses
}
#endregion
# Reset a Users password
# Note that synchronized users need Azure AD Premium, and Azure AD Connect with Password Write-Back Configured
$password = Read-Host AsSecureString
Set-AzureADUserPassword ObjectId <myuserupn or objectid> Password $password
# Change (not Reset) the current logged on users password
Update-AzureADSignedInUserPassword CurrentPassword $CurrentPassword NewPassword $NewPassword

In the next part of my session I went on to talk about the Azure AD Graph API and the Microsoft Graph API. The Microsoft Graph API will eventually be the “one API to rule them all”, as Azure AD also can be accessed by that API, but there are still use cases for the Azure AD Graph API.

In either case, to be able to use the APIs you must create and register an Azure AD Application of type Web App/Api, and give that Application the needed permissions to access the APIs. I showed in my session how to do this in the portal, and here you have a PowerShell Script for creating that same type of Application, this example for accessing the Azure AD Graph API:


# This Application is for accessing the Azure AD Graph Api
# Log in to Azure AD with Global Admin
Connect-AzureAD
# Create the Azure AD API Application
$azureAdApp = New-AzureADApplication DisplayName "Elven Azure AD Reporting Api App" Homepage "https://localhost" IdentifierUris "https://localhost/azureadreportingapi" ReplyUrls "https://localhost"
$keyStartDate = "{0:s}" -f (get-date).AddHours(-1) + "Z"
$keyEndDate = "{0:s}" -f (get-date).AddYears(1) + "Z"
# Create Password Key Secret
$azureAdAppKeySecret = New-AzureADApplicationPasswordCredential ObjectId $azureAdApp.ObjectId CustomKeyIdentifier "Azure AD Api Reporting Key" StartDate $keyStartDate EndDate $keyEndDate
# Get the Azure AD SPN
$azureAdSpn = Get-AzureADServicePrincipal Filter "DisplayName eq 'Microsoft.Azure.ActiveDirectory'"
# Get the Oauth2 permissions for Read and Sign-in plus Directory Read
$azureAdOauth2UserSignInProfileRead = $azureAdSpn | select expand Oauth2Permissions | ? {$_.value -eq "User.Read"}
$azureAdOauth2DirectoryRead = $azureAdSpn | select expand Oauth2Permissions | ? {$_.value -eq "Directory.Read.All"}
# Build a Required Resource Access Object with permissions for User.Read + Sign in and Directory Read
$requiredResourceAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
  ResourceAppId=$azureAdSpn.AppId ;
  ResourceAccess=[Microsoft.Open.AzureAD.Model.ResourceAccess]@{
    Id = $azureAdOauth2UserSignInProfileRead.Id ;
    Type = "Scope"
},
[Microsoft.Open.AzureAD.Model.ResourceAccess]@{
Id = $azureAdOauth2DirectoryRead.Id ;
Type = "Role"
}
}
# Set the required resources for the Azure AD Application
Set-AzureADApplication ObjectId $azureadapp.ObjectId RequiredResourceAccess $requiredResourceAccess
# Associate a new Service Principal to my Azure AD Application
$appspn = New-AzureADServicePrincipal AppId $azureadapp.AppId Tags @("WindowsAzureActiveDirectoryIntegratedApp")
# Add Permission Grant for that App Service Principal to the Microsoft.Azure.ActiveDirectory API
## This is the only thing that cannot be automated by now!
### Go to the Azure Portal and your Azure AD, under App Registrations, find this Reporting Api App, and under Permissions select to Grant Permission

Note that for the above script, you will need to note some output and manual operations:

  • Take a note of the Application ID, you will need that later:
    azureadapp
  • Take note of the Key Secret, you will need that later also:
    azureadappkeysecret
  • Application must be manually granted permission here, as this per now cannot be automated with PowerShell:
    azureadappgrantpermission

By the way, you should newer share this App Id and key secret publically (as I have just done here 😉 Other people could use that same information to access your APIs and Azure AD info, so take care to protect that info! (Of course I have deleted that info after showing this here 😉

Now, with this App registered in Azure AD, we can now start managing Azure AD via REST API calls, for example from PowerShell. The following script shows how we can get Self Service Password Registration Activity via the Azure AD Graph API, specifically we will use the Reporting API (https://msdn.microsoft.com/en-us/library/azure/ad/graph/howto/azure-ad-reports-and-events-preview). Note that the script will need the App Id and Key value noted from above:


# PowerShell for calling the Azure AD Graph Reporting REST API, https://msdn.microsoft.com/en-us/library/azure/ad/graph/howto/azure-ad-reports-and-events-preview
# Getting Self Service Password Reset Registrations
# This script will require registration of a Web Application in Azure Active Directory
# Method 1: Use steps here for manually creating required Web App: https://docs.microsoft.com/en-us/azure/active-directory/active-directory-reporting-api-prerequisites
# Method 2: Use Azure AD PowerShell as documented here: https://gist.github.com/skillriver/b46c51e2902a331a91221c6828bd320c#file-azureadapiapplication-ps1
$loginURL = "https://login.microsoftonline.com"
$tenantdomain = "<yourtenant>.onmicrosoft.com"
# Fill in your App Id and Key Secret
$azureAdAppId = "<app id for azure ad application>"
$azureAdAppKey = "<valid key secret for azure ad application>"
# Create a credential based on already registered Azure AD App Id and Key Secret
$keysecurestring = ConvertTo-SecureString $azureAdAppKey AsPlainText Force
$reportingapicred = New-Object System.Management.Automation.PSCredential ($azureAdAppId, $keysecurestring)
# Get an Oauth 2 access token based on client id, secret and tenant domain
$body = @{grant_type="client_credentials";resource=$resource;client_id=$reportingapicred.UserName;client_secret=$reportingapicred.GetNetworkCredential().Password}
$oauth = Invoke-RestMethod Method Post Uri $loginURL/$TenantDomain/oauth2/token?apiversion=1.0 Body $body
# Define a header with the authorization token
$headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"}
# Build the request, here we are looking for SSPR activity
$topResults = 100 # Tweak this value if you want different page size and present it in a report
$reportContent = @()
$reportUrl = "https://graph.windows.net/$TenantDomain/reports/ssprRegistrationActivityEvents?api-version=beta&`$top=$topResults"
$reportCount = 0
# Returns a JSON document for the "ssprRegistrations" report
$ssprRegistrations = (Invoke-WebRequest Headers $headerParams Uri $reportUrl UseBasicParsing).Content | ConvertFrom-Json
# Adding data to the Report
$reportContent += $ssprRegistrations.value | Select Unique eventTime, role, registrationActivity, displayName, userName
# Showing the Report
$reportContent
# Exporting the Report to a Comma Separated Value file
$reportContent | Export-Csv "ElvenAzureAD_SSPRregistrations.csv" NoTypeInformation Delimiter ","

With that last export to a Csv file I can import it to Power BI as a table, and create a report and a dashboard on it, for example showing which password reset registration method the users configured, what user and role type did the registration and the count and date for the registrations:

PowerBIReport.PNG

In the session we also looked at the new Content Pack for Azure AD, showing sign-in and audit events, and also how you can get data from the Microsoft Graph API using a OData Feed:

I hope this scripts will be as useful for you as it is for me! Good luck with taking your management of Azure AD to the next level with Azure AD PowerShell and Graph APIs!

How to use PowerShell script for setting Azure AD Password Reset Writeback On-premises Permissions

When you configure the Azure AD Premium Self Service Password Reset solution on your Azure AD tenant and then the Azure AD Connect Password Writeback feature, you will need to add permissions in your local Active Directory that permits the Azure AD Connect account to actually change and reset passwords for your users  , as detailed here: https://docs.microsoft.com/en-us/azure/active-directory/active-directory-passwords-getting-started#step-4-set-up-the-appropriate-active-directory-permissions.

I wrote this PowerShell script that helps you configure this correctly in your domain/forest. Some notes:

  • You can use it in a single-domain, single-forest domain, or in a multi-domain forest, just remember to specify a Domain Controller for the wanted domain, and for the domain the Azure AD Connect account is in.
  • You have to find the Azure AD Connect Synchronization account, it would be MSOL_xxxx.. if you have used Express settings, or a dedicated account. Look at current configuration for details.
  • You can specify an OU for your users, and if inheritance is enabled all subordinate users and OUs will inherit the permissions. If not, please run the script once for each OU you want the permissions to be applied for.

Here is the script:


# Description: Sets Azure AD Connect Password Write Back AD Permissions
# Created by: Jan Vidar Elven, Enterprise Mobility MVP, Skill AS
# Last Modified: 01.06.2016
# Run this on-premises for your domain/forest
Import-Module ActiveDirectory
#region Initial Parameters/Variables
# Domain Controller in wanted domain, leave blank if using current domain
$dcserver = "mydc.domain.local"
# Azure AD Connect Synchronization Account
$aadcaccount = "MSOL_xxxxx"
# Azure AD Connect Account Domain Controller
$aadcserver = "mydc.domain.local"
# Organizational Unit Distinguished Name, starting point for delegation
$oudn = "OU=<UsersOU>,DC=domain,DC=local"
#endregion
# Check if default current domain or specified domain should be used
If ($dcserver -eq $null) {
# Get a reference to the RootDSE of the current domain
$rootdse = Get-ADRootDSE
# Get a reference to the current domain
$domain = Get-ADDomain
}
Else {
# Get a reference to the RootDSE of the domain for the specified server
$rootdse = Get-ADRootDSE Server $dcserver
# Get a reference to the domain for the specified server
$domain = Get-ADDomain Server $dcserver
}
# Refer to my Active Directory, either current or specified server from above
New-PSDrive Name "myAD" Root "" PsProvider ActiveDirectory Server $rootdse.dnsHostName
# Change to My Active Directory Command Prompt
cd myAD:
# Create a hashtable to store the GUID value of each schema class and attribute
$guidmap = @{}
Get-ADObject SearchBase ($rootdse.SchemaNamingContext) LDAPFilter `
"(schemaidguid=*)" Properties lDAPDisplayName,schemaIDGUID |
% {$guidmap[$_.lDAPDisplayName]=[System.GUID]$_.schemaIDGUID}
# Create a hashtable to store the GUID value of each extended right in the forest
$extendedrightsmap = @{}
Get-ADObject SearchBase ($rootdse.ConfigurationNamingContext) LDAPFilter `
"(&(objectclass=controlAccessRight)(rightsguid=*))" Properties displayName,rightsGuid |
% {$extendedrightsmap[$_.displayName]=[System.GUID]$_.rightsGuid}
# Get a reference to the OU we want to delegate
$ou = Get-ADOrganizationalUnit Identity $oudn
# Get the SID value of the Azure AD Connect Sync Account we wish to delegate access to
$a = New-Object System.Security.Principal.SecurityIdentifier (Get-ADUser $aadcaccount Server $aadcserver).SID
# Get a copy of the current DACL on the OU
$acl = Get-ACL Path ($ou.DistinguishedName)
# Create an Access Control Entry for new permission we wish to add
# Allow the Azure AD Account to reset passwords on all descendent user objects
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule `
$a,"ExtendedRight","Allow",$extendedrightsmap["Reset Password"],"Descendents",$guidmap["user"]))
# Allow the Azure AD Account to change passwords on all descendent user objects
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule `
$a,"ExtendedRight","Allow",$extendedrightsmap["Change Password"],"Descendents",$guidmap["user"]))
# Allow the Azure AD Account to write lockoutTime extended property
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule `
$a,"WriteProperty","Allow",$guidmap["lockoutTime"],"Descendents",$guidmap["user"]))
# Allow the Azure AD Account to write pwdLastSet extended property
$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule `
$a,"WriteProperty","Allow",$guidmap["pwdLastSet"],"Descendents",$guidmap["user"]))
# Re-apply the modified DACL to the OU
Set-ACL ACLObject $acl Path ("myAD:\"+($ou.DistinguishedName))

Hope the script will be helpful!

Exchange Online PowerShell with Modern Authentication and Azure MFA available!

A while back I wrote a blog post on how you could use Azure AD Privileged Identity Management to indirectly require MFA for Office 365 Administrator Roles activation before they connected to Exchange online via Remote PowerShell. See https://gotoguy.blog/2016/09/09/how-to-enable-azure-mfa-for-online-powershell-modules-that-dont-support-mfa/.

In december a new Exchange Online Remote PowerShell Module was released (in preview), https://technet.microsoft.com/en-us/library/mt775114(v=exchg.160), that uses Modern Authentication and that supports Azure Multi-Factor Authentication. Lets try it out:

First you need to verify that Modern Authentication is enabled in your Exchange Online organization, as this is not enabled by default: https://support.office.com/en-us/article/Enable-Exchange-Online-for-modern-authentication-58018196-f918-49cd-8238-56f57f38d662?ui=en-US&rs=en-US&ad=US

In my Exchange Online organization I verify that Modern Authentication is enabled:

image

Next logon to your Exchange Online Admin Center, and go to Hybrid to download and configure the Exchange Online PowerShell Module:

image

The configure button activates a click once install:

image

After installation I’m ready to connect:

image

Lets try it out on a MFA enabled admin user:

image

And as expected, I’m prompted to provide my verification code:

image

And after verification I can administer Exchange Online:

image

So with that we are finally able to log in to Exchange Online PowerShell more securely with Azure Multi-Factor Authentication as long as Modern Authentication is enabled for your organization!

Experts and Community unite at last ever #SCU_Europe 2016! #ExpertsLive next

This years SCU Europe 2016, for the first time outside Switzerland in the 4th year running, was held in Berlin at the BCC (Berlin Congress Center) close to the Alexander Platz in the eastern parts of “Berlin Mitte”.

 

 

The intro video introducing the Experts:

Let’s begin with the end: at the closing note SCUE general Marcel Zehner announced and with a little bit of emotion that this was the last ever SCU Europe to be held.. You and your organization should be proud of what you have achieved, Marcel, it is one of the best community conferences around, and I have been fortunate to be able to visit all 4 starting with Bern in 2013, Basel in 2014 and 2015, and now Berlin in 2016. It’s only cities with B’s is it? In fact, you never know what twists and turns your career takes, but looking back I’m not sure I would be where I am now in turn of being presenter, MVP and community influencer myself if I had not travelled alone to Bern 4 years ago, that’s where I really started working with and for the Community (with a capitol C)!

Luckily SCU Europe will continue as Experts Live Europe next year! Same place at BCC, same organization and format, and the same dates only next year it will be: 23rd – 25th of August 2017. A new web page was launched, www.expertslive.eu, and Twitter (@ExpertsLiveEU) and Facebook have been changed to reflect that. The hash tag #SCU_Europe will eventually be inactive and you should now use #ExpertsLive.

image

I think this is a very good decision, there has already been discussion on that the name “System Center Universe” is not really reflecting the content and focus of the conference, now embracing the Cloud, with content areas for Management, Productivity, Security, DevOps, Automation, Data Platform and more. ExpertsLive, originally a 1-day community conference in Netherland running each year back from 2009 and with up to 1200 participants, will now be a network of conferences, ranging from region based (ExpertsLive Europe, but also SCU APAC and SCU Australia will be ExpertsLive APAC and Australia next year), and local, country based ExpertsLive like the one in Netherlands, but more will come.

image

The closing note video announcing Experts Live Europe:

This year at SCU Europe I was one of the Experts and presented two sessions on “Premium Identity Management and Protection with Azure AD” and “Deep Dive: Publishing Applications with Azure AD”. I also took part in a “Ask-the-Experts” area together with Cameron Fuller and Kevin Greene where we took questions on the topic System Center 2016. I participated on a discussion panel on Friday morning with Markus Wilhelm from Microsoft Germany on the subject Defense Strategies and Security, and of course we had the Meet and greet with the Experts at the Networking party. It was a really great experience speaking at this conference, thanks for having me!

 

 

 

 

The content of the conference this year was great, and for the first time there was 5 tracks, with over 70 sessions presented! All presentations and session recordings will be at Channel 9 in a few weeks time, so make sure you look at anything you missed or want to see again if you where there, or if you weren’t at the conference this year you can look at your sessions of interest.

I was travelling with a group this year, both from my company and some of our customers, in total we were 7 in the group, and also had 3 cancellations the last week before the conference from some customers that could not make it after all. Moving the conference to Berlin is a big part of why it now was easier to attract more Nordic attendance I think. We stayed at the Park Inn by Radisson right by the Alexander Platz and BCC, so it was really central and nice.

 

 

 

 

In good tradition there are a lot of parties and social networking going on. On the first night there are the Sponsors and Speakers Party, which was held in Mio right by the TV Tower by Alexander Platz, on Thursday we had the attendee Networking Party at the conference center. Later that night our group and some more partners/customers of Squared Up went on to another party at Cosmic Kaspar. It was really hot, so basically the party was at the pavement! On the last day we had the Closing Drinks, sponsored by Cireson and itnetX at Club Carambar, also close to the Alexander Platz. In addition, there are a lot of unofficial gatherings going on, lots of laughs and new and old friends have a good time.

 

 

 

 

 

 

See you next year at Experts Live Europe in Berlin 23-25th August, 2017!

Trigger Azure AD Connect Sync Scheduler with Azure Automation

In this blog post I will show how you can trigger the Azure AD Connect Sync Scheduler with an Azure Automation Runbook PowerShell Script. Since the Azure AD Connect build 1.1.105.0 was released February 2016, a new scheduler is built-in that per default sync every 30 minutes (previously 3 hours). For more detail on Azure AD Connect Sync Scheduler, see https://azure.microsoft.com/en-us/documentation/articles/active-directory-aadconnectsync-feature-scheduler/.

Normally a sync schedule of 30 minutes is sufficient for most use, but sometimes you will need to do an immediate sync. So I thought it was a good idea to create a PowerShell script that creates a remote session to the Azure AD Connect Server, and then triggers a delta sync.

Now, this PowerShell script can of course be used with any of your favorite automation solutions, for example Orchestrator or SMA on-premises. But why not just use Azure Automation and a Hybrid Worker to run this script. This way you can trigger the script in a number of ways including in the Azure Portal, via Webhooks, remediating alerts in OMS and more.

Requirements

Lets first take a look at the requirements for this solution:

  • You will have to have an Azure Subscription, so that an Azure Automation Account can be created (or use your existing account), and that a runbook script with the related assets can be created.
  • You will need to have an OMS Workspace for the Azure Subscription, and have a Hybrid Worker set up that can communicate with the Azure AD Connect Server. The Hybrid Worker will use a credential asset and variable asset created in the first part.

In the following two parts I will look at these two requirements and how you can set it up to start triggering Azure AD Connect Scheduler with your Azure Automation Runbook.

Part 1 – Set up the Azure Automation Runbook and Assets

To set up the Azure Automation part of the solution, I have created a GitHub Repository  where you can deploy the solution directly from https://github.com/skillriver/Trigger-AzureADSyncScheduler. This repository contains the Azure Resource Manager deployment template and PowerShell script that you need to get started.

You can also click this deploy button directly:

 

Lets step through what you experience when you click to “Deploy to Azure”. Please make sure that you are logged in to your correct Azure Subscription first.

Deploying with the Template

I will not go through how I created the ARM based JSON templates, but I will quickly show the user experience when doing the deployment.

The custom deployment will ask you for some parameter values:

  • AUTOMATIONACCOUNTNAME. If you specify an existing Automation Account, this will be used, or else a new one will be created with the Free pricing tier.
  • AAHYBRIDWORKERCREDENTIALNAME. There is a default value there, this will be used as a Credential Asset in the PowerShell script. You can change the value, but then you must remember to change it in the script as well.
  • AAHYBRIDWORKERDOMAIN. The NETBIOS Domain Name for where the Azure AD Connect Server belongs to.
  • AAHYBRIDWORKERUSERNAME. This is this the user name for the service account or other user account that has permission to connect to the Azure AD Connect Server and trigger the sync schedule.
  • AAHYBRIDWORKERPASSWORD. The password for the user above.
  • AADSSERVERNAME. The server name of the Azure AD Connect Server.

You will have to select the correct subscription, and either create a new Resource Group, or an existing one (please note that Azure Automation is not available in every region).

image

After saving the parameters, and reviewing and accepting legal terms, you are ready to create the custom deployment.

If everything went OK, you should see a confirmation:

image

You will have an Automation Account:

image

You will have a PowerShell Script Runbook with the name Trigger-AzureADSync:

image

The script can be viewed as shown below. This script is short and simple, it will get the Asset Variable for Azure AD Connect Server name, and get the Credential Asset for the Hybrid Worker Account. It will the create a remote session, and run the delta sync cycle:

image

Lets take a look at the Assets created with the deployment as well, the Variable:

image

The Credential:

image

That’s the whole solution for this first part. If you for any reason could not or would not deploy the template directly, and would prefer to create this manually, you should be fine just following the images above. Just follow these steps:

  1. Create a Azure Automation Account (Free tier, and in your chosen supported Azure Region).
  2. Create a Variable Asset, with the name of the Azure AD Connect Server.
  3. Create a Credential Asset, with the DOMAIN\UserName of the account you will use to remote session to the Azure AD Connect Server.
  4. Create a new PowerShell Script Runbook, typing the CmdLets from above and using your variable assets.

By now you should be ready for the next step, because you cannot run this Automation Runbook just yet. You have to have in place OMS and a Hybrid Worker first, and that will be shown in the next part.

Part 2 –  Set up the Hybrid Worker and Remote session permission

To be able to run Azure Automation Runbooks in your own datacenter, you will need to have an OMS workspace and at least one Hybrid Worker configured that will be able to execute the Runbook locally and connect to the Azure AD Connect Server.

Hybrid Runbook Worker Components

I will not go through the details here on how to set up an OMS workspace and a Hybrid Worker if you don’t have this from before, you can just follow the documentation here https://azure.microsoft.com/en-us/documentation/articles/automation-hybrid-runbook-worker/.

After setting up and registering your Hybrid Worker, you will have a Hybrid Worker Group with at least one Hybrid Worker.

image

Now, running the Runbook with the right security is going to be essential here, after all the Runbook is going to connect to the Azure AD Connect Server and initiate the sync cycle. Lets first check the settings of the Hybrid Worker Group. We can either select a Default Run As account as I have here:

image

Or you can select a Custom Run As, specifying a credential Asset to use for all Runbooks running on this Hybrid Worker Group:

image

In my example here, I will use the Default Run As Account, because I specify my own credentials in the PowerShell Runbook, as shown earlier in Part 1 of this blog post:

image

Next, I will have to create a domain account in my local Active Directory. I have created a service account to be used for Azure Automation Hybrid Workers. This is the same account you specified when creating the credential asset in Part 1 in Azure Automation:

image

This account will need permission to remote PowerShell to the Azure AD Connect Server. In Computer Management and Local Users and Groups on the Azure AD Connect Server, add this service account to the Remote Management Users group:

image

And add the account to the ADSyncOperators group, so that the user has permission to Azure AD Connect operations:

image

That should be it, we are now ready to start the Runbook and verify that it works.

Starting the Runbook

From the Automation Account and the Trigger-AzureADSync Runbook, select Start and under Run Settings select Hybrid Worker and your Hybrid Worker Group:

image

You can verify that the job completed and with no errors:

image

Looking into the Synchronization Service on the Azure AD Connect Server, I can verify that the sync cycle has been running:

image

That concludes this blog article, hope it has been helpful!

Displaying Azure Automation Runbook Stats in OMS via Performance Collection and Operations Manager

Wouldn’t it be great to get some more information about your Azure Automation Runbooks in the Operations Management Suite Portal? That’s a rhetorical question, of course the answer will be yes!

While Azure Automation is a part of the suite of components in OMS, today you only get the following information from the Azure Automation blade:

The blade shows the number of runbooks and jobs from the one Automation Account you have configured. You can only configure one Automation Account at a time, and for getting more details you are directed to the Azure Portal.

I wanted to use my OMS-connected Operations Manager Management Group, and use a PowerShell script rule to get some more statistics for Azure Automation and display that in OMS Log Analytics as Performance Data. I will do this using the “Sample Management Pack – Wizard to Create PowerShell script Collection Rules” described in this blog article http://blogs.msdn.com/b/wei_out_there_with_system_center/archive/2015/09/29/oms-collecting-nrt-performance-data-from-an-opsmgr-powershell-script-collection-rule-created-from-a-wizard.aspx.

I will use the AzureRM PowerShell Module for the PowerShell commands that will connect to my Azure subscription and get the Azure Automation Runbooks data.

Getting Ready

Before I can create the PowerShell script rule for gettting the Azure Automation data, I have to do some preparations first. This includes:

  1. Importing the “Sample Management Pack – Wizard to Create PowerShell script Collection Rules” to my Operations Manager environment.
    1. This can be downloaded from Technet Gallery at https://gallery.technet.microsoft.com/Sample-Management-Pack-e48040f7.
  2. Install the AzureRM PowerShell Module (at the chosen Target server for the PowerShell Script Rule).
    1. I chose to install it from the PowerShell Gallery using the instructions here: https://azure.microsoft.com/en-us/documentation/articles/powershell-install-configure/
    2. If you are running Windows Server 2012 R2, which I am, follow the instructions here to support the PowerShellGet module, https://www.powershellgallery.com/GettingStarted?section=Get%20Started.
  3. Choose Target for where to run the AzureRM commands from
    1. Regarding the AzureRM and where to install, I decided to use the SCOM Root Management Server Emulator. This server will then run the AzureRM commands against my Azure Subscription.
  4. Choose account for Run As Profile
    1. I also needed to think about the run as account the AzureRM commands will run under. As we will see later the PowerShell Script Rules will be set up with a Default Run As Profile.
    2. The Default Run As Profile for the RMS Emulator will be the Management Server Action Account, if I had chosen another Rule Target the Default Run As Profile would be the Local System Account.
    3. Alternatively, I could have created a custom Run As Profile with a user account that have permissions to execute the AzureRM cmdlets and connect to and read the required data from the Azure subscription, and configure the PowerShell Script rules to use that.
    4. I decided to go with the Management Server Action Account, in my case SKILL\scom_msaa. This account will execute the AzureRM PowerShell cmdlets, so I need to make sure that I can login to my Azure subscription using that account.
  5. Next, I started PowerShell ISE with “Run as different user”, specifying my scom_msaa account. I run the commands below, as I wanted to save the password for the user I’m going to connect to the Azure subscription and get the Automation data. I also did a test import-module of the AzureRM modules I will need in the main script.

The commands above are here in full:


# Prepare to save encrypted password

# Verify that logged on as scom_msaa
whoami

# Get the password
$securepassword = Read-Host -AsSecureString -Prompt Enter Azure AD account password:

# Filepath for encrypted password file
$filepath = C:\users\scom_msaa\AppData\encryptedazureadpassword.txt

# Save password encrypted to file
ConvertFrom-SecureString -SecureString $securepassword | Out-File -FilePath $filepath

Import-Module C:\Program Files\WindowsPowerShell\Modules\AzureRM
Import-Module C:\Program Files\WindowsPowerShell\Modules\AzureRM.Profile
Import-Module C:\Program Files\WindowsPowerShell\Modules\AzureRM.Automation

At this point I’m ready for the next step, which is to create some PowerShell commands for the Script Rule in SCOM.

Creating the PowerShell Command Script for getting Azure Automation data

First I needed to think about what kind of Azure Automation and Runbook data I wanted to get from my Azure Subscription. I decided to get the following values:

  • Job Count Last Day
  • Job Count Last Month
  • Job Count This Month
  • Job Minutes This Month
  • Runbooks in New State
  • Runbooks in Published State
  • Runbooks in Edit State
  • PowerShell Workflow Runbooks
  • Graphical Runbooks
  • PowerShell Script Runbooks

I wanted to have the statistics for Runbooks Jobs to see the activity of the Runbooks. As I’m running the Free plan of Azure Automation, I’m restricted to 500 minutes a month, so it makes sense to count the accumulated job minutes for the month as well.

In addition to this I want some statistics for the number of Runbooks in the environment, separated on New, Published and Edit Runbooks, and the Runbook type separated on Workflow, Graphical and PowerShell Script.

The PowerShell Script Rule for getting these data will be using the AzureRM PowerShell Module, and specifically the cmdlets in AzureRM.Profile and AzureRM.Automation:

To log in and authenticate to Azure, I will use the encrypted password saved earlier, and create a Credential object for the login:

Initializing the script with date filters and setting default values for variables. I decided to create the script so that I can get data from all the Resource Groups I have Automation Accounts in. This way, If I have multiple Automation Accounts, I can get statistics combined for each of them:

Then, looping through each Resource Group, running the different commands to get the variable data. Since I potentially will loop through multiple Resource Groups and Automation Accounts, the variables will be using += to add to the previous loop value:

After setting each variable and exiting the loop, the $PropertyBag can be filled with the values for the different counters:

The complete script is shown below for how to get those Azure Automation data via SCOM and PowerShell Script Rule to to OMS:


# Debug file
$debuglog = $env:TEMP+\powershell_perf_collect_AA_stats_debug.log

Date | Out-File $debuglog

Who Am I: | Out-File $debuglog -Append
whoami |
Out-File $debuglog -Append

$ErrorActionPreference = Stop

Try {

If (!(Get-Module –Name AzureRM)) { Import-Module C:\Program Files\WindowsPowerShell\Modules\AzureRM }
If (!(Get-Module –Name AzureRM.Profile)) { Import-Module C:\Program Files\WindowsPowerShell\Modules\AzureRM.Profile }
If (!(Get-Module –Name AzureRM.Automation)) { Import-Module C:\Program Files\WindowsPowerShell\Modules\AzureRM.Automation }

# Get Cred for ARM
$filepath = C:\users\scom_msaa\AppData\encryptedazureadpassword.txt
$userName = myAzureADAdminAccount
$securePassword = ConvertTo-SecureString (Get-Content -Path $FilePath)
$cred = New-Object -TypeName System.Management.Automation.PSCredential ($username, $securePassword)

# Log in and sett active subscription
Login-AzureRmAccount -Credential $cred

$subscriptionid = mysubscriptionID

Set-AzureRmContext -SubscriptionId $subscriptionid

$API = new-object -comObject MOM.ScriptAPI

$aftertime = $(Get-Date).AddHours(1)
$afterdate_lastday = $(Get-Date).AddDays(1)
$afterdate_lastmonth = $(Get-Date).AddDays(30)
$afterdate_thismonth = $(Get-Date).AddDays(($(Get-Date).Day)+1)

$AutomationRGs = @(MyResourceGroupName1,MyResourceGroupName2)

$PropertyBags[email protected]()

$newrunbooks = 0
$publishedrunbooks = 0
$editrunbooks = 0
$scriptrunbooks = 0
$graphrunbooks = 0
$powershellrunbooks = 0
$jobcountlastday = 0
$jobcountlastmonth = 0
$jobcountthismonth = 0
$jobminutesthismonth = 0

ForEach ($AutomationRG in $AutomationRGs) {

$rmautomationacct = Get-AzureRmAutomationAccount -ResourceGroupName $AutomationRG

$newrunbooks += (Get-AzureRmAutomationRunbook -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
|
Where {$_.State -eq New}).Count

$publishedrunbooks += (Get-AzureRmAutomationRunbook -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
|
Where {$_.State -eq Published}).Count

$editrunbooks += (Get-AzureRmAutomationRunbook -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
|
Where {$_.State -eq Edit}).Count

$scriptrunbooks += (Get-AzureRmAutomationRunbook -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
|
Where {$_.RunbookType -eq Script}).Count

$graphrunbooks += (Get-AzureRmAutomationRunbook -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
|
Where {$_.RunbookType -eq Graph}).Count

$powershellrunbooks += (Get-AzureRmAutomationRunbook -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
|
Where {$_.RunbookType -eq PowerShell}).Count

$jobcountlastday += (Get-AzureRmAutomationJob -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
-StartTime
$afterdate_lastday).Count

$jobcountlastmonth += (Get-AzureRmAutomationJob -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
-StartTime
$afterdate_lastmonth).Count

$jobcountthismonth += (Get-AzureRmAutomationJob -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
-StartTime
$afterdate_thismonth.ToLongDateString()).Count

$jobsthismonth = Get-AzureRmAutomationJob -AutomationAccountName $rmautomationacct.AutomationAccountName -ResourceGroupName $AutomationRG `
-StartTime
$afterdate_thismonth.ToLongDateString() | Select-Object RunbookName, StartTime, EndTime, CreationTime, LastModifiedTime, @{Name=RunningTime;Expression={[TimeSpan]::Parse($_.EndTime $_.StartTime).TotalMinutes}}, @{Name=Month;Expression={($_.EndTime).Month}}

$jobminutesthismonth += [int][Math]::Ceiling(($jobsthismonth | Measure-Object -Property RunningTime -Sum).Sum)

}

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Job Count Last Day)
$PropertyBag.AddValue(Value, [UInt32]$jobcountlastday)
$PropertyBags += $PropertyBag

Job Count Last Day: | Out-File $debuglog -Append
$jobcountlastday | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Job Count Last Month)
$PropertyBag.AddValue(Value, [UInt32]$jobcountlastmonth)
$PropertyBags += $PropertyBag

Job Count Last Month: | Out-File $debuglog -Append
$jobcountlastmonth | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Job Count This Month)
$PropertyBag.AddValue(Value, [UInt32]$jobcountthismonth)
$PropertyBags += $PropertyBag

Job Count This Month: | Out-File $debuglog -Append
$jobcountthismonth | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Job Minutes This Month)
$PropertyBag.AddValue(Value, [UInt32]$jobminutesthismonth)
$PropertyBags += $PropertyBag

Job Minutes This Month: | Out-File $debuglog -Append
$jobminutesthismonth | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Runbooks in New State)
$PropertyBag.AddValue(Value, [UInt32]$newrunbooks)
$PropertyBags += $PropertyBag

Runbooks in New State: | Out-File $debuglog -Append
$newrunbooks | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Runbooks in Published State)
$PropertyBag.AddValue(Value, [UInt32]$publishedrunbooks)
$PropertyBags += $PropertyBag

Runbooks in Published State: | Out-File $debuglog -Append
$publishedrunbooks | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Runbooks in Edit State)
$PropertyBag.AddValue(Value, [UInt32]$editrunbooks)
$PropertyBags += $PropertyBag

Runbooks in Edit State: | Out-File $debuglog -Append
$editrunbooks | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, PowerShell Workflow Runbooks)
$PropertyBag.AddValue(Value, [UInt32]$scriptrunbooks)
$PropertyBags += $PropertyBag

PowerShell Workflow Runbooks: | Out-File $debuglog -Append
$scriptrunbooks | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Graphical Runbooks)
$PropertyBag.AddValue(Value, [UInt32]$graphrunbooks)
$PropertyBags += $PropertyBag

Graphical Runbooks: | Out-File $debuglog -Append
$graphrunbooks | Out-File $debuglog -Append

$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, PowerShell Script Runbooks)
$PropertyBag.AddValue(Value, [UInt32]$powershellrunbooks)
$PropertyBags += $PropertyBag

PowerShell Script Runbooks: | Out-File $debuglog -Append
$powershellrunbooks | Out-File $debuglog -Append

$PropertyBags

} Catch {

Error Catched: | Out-File $debuglog -Append
$(
$_.Exception.GetType().FullName) | Out-File $debuglog -Append
$(
$_.Exception.Message) | Out-File $debuglog -Append

}

PS! I have included debugging and logging in the script, be aware though that doing $ErrorActionPreference=Stop will end the script if any errors, for example with logging, so it might be an idea to remove the debug logging when confirmed that everything works.

In the next part I’m ready to create the PowerShell Script Rule.

Creating the PowerShell Script Rule

In the Operations Console, under Authoring, create a new PowerShell Script Rule as shown below:

  1. Select the PowerShell Script (Performance – OMS Bound) Rule:I have created a custom destination management pack for this script.
  2. Specifying a Rule name and Rule Category: Performance Collection. As mentioned earlier in this article the Rule target will be the Root Management Server Emulator:
  3. Selecting to run the script every 30 minutes, and at which time the interval will start from:
  4. Selecting a name for the script file and timeout, and entering the complete script as shown earlier:
  5. For the Performance Mapping information, the Object name must be in the \\FQDN\YourObjectName format. For FQDN I used the Target variable for PrincipalName, and for the Object Name AzureAutomationRunbookStats, and adding the “\\” at the start and “\” between: \\$Target/Host/Property[Type=”MicrosoftWindowsLibrary7585010!Microsoft.Windows.Computer”]/PrincipalName$\AzureAutomationRunbookStatsI specified the Counter name as “Azure Automation Runbook Stats”, and the Instance and Value are specified as $Data/Property(@Name=’Instance’)$ and $Data/Property(@Name=Value)$. These reflect the PropertyBag instance and value created in the PowerShell script:
  6. After finishing the Create Rule Wizard, two new rules are created, which you can find by scoping to the Root Management Server Emulator I chose as target. Both Rules must be enabled, as they are not enabled by default:

At this point we are finished configuring the SCOM side, and can wait for some hours to see that data are actually coming into my OMS workspace.

Looking at Azure Automation Runbook Stats Performance Data in OMS

After a while I will start seeing Performance Data coming into OMS with the specified Object and Counter Name, and for the different instances and values.

In Log Search, I can specify Type=Perf ObjectName=AzureAutomationRunbookStats, and I will find the Results and Metrics for the specified time frame.

In the example above I’m highlighting the Job Minutes This Month counter, which will steadily increase for each month, and as we can see the highest value was 107 minutes, after when the month changed to March we were back at 0 minutes. After a while when the number of job minutes increases it will be interesting to follow whether this counter will go close to 500 minutes.

This way, I can now look at Azure Automation Runbook stats as performance data, showing different scenarios like how many jobs and runbook job minutes there are over a time period. I can also look at how what type of runbooks I have and what state they are in.

I can also create saved searches and alerts for my search criteria.

Creating OMS Alerts for Azure Automation Runbook Counters

There is one specific scenario for Alerts I’m interested in, and that is when I’m approaching my monthly limit on 500 job minutes.

“Job Minutes This Month” is a counter that will get the sum of all job minutes for all runbook jobs across all automation accounts. In the classic Azure portal, you will have a usage overview like this:

With OMS I would get this information over a time period like this:

The search query for Job Minutes This Month as I have defined it via the PowerShell Script Rule in OMS is:

Type=Perf ObjectName=AzureAutomationRunbookStats InstanceName=”Job Minutes This Month”

This would give me all results for the defined time period, but to generate an alert I would want to look at the most recent results, for example for the last hour. In addition, I want to filter the results for my alert when the number of job minutes are over the threshold of 450 which means I’m getting close to the limit of 500 free minutes per month. My query for this would be:

Type=Perf ObjectName=AzureAutomationRunbookStats InstanceName=”Job Minutes This Month” AND TimeGenerated>NOW-1HOUR AND CounterValue > 450

Now, in my test environment, this will give med 0 results, because I’m currently at 37 minutes:

Let’s say, for sake of testing an Alert, I add criteria to include 37 minutes as well, like this:

This time I have 2 results. Let’s create an alert for this, press the ALERT button:

For the alert I give it a name and base it on the search query. I want to check every 60 minutes, and generate alert when the number of results is greater than 1 so that I make sure the passing of the threshold is consistent and not just temporary.

For actions I want an email notification, so I type in a Subject and my recipients.

I Save the alert rule, and verify that it was successfully created.

Soon I get my first alert on email:

Now, that it works, I can remove the Alert and create a new one without the OR CounterValue=37, this I leave to you 😉

With that, this blog post is concluded. Thanks for reading, I hope this post on how to get more insights on your Azure Automation Runbook Stats in OMS and getting data via NRT Perfomance Collection has been useful 😉

Collecting Service Manager Incident Stats in OMS via PowerShell Script Performance Collection in Operations Manager

I have been thinking about bringing in some key Service Manager statistics to Microsoft Operations Management Suite. The best way to do that now is to use the NRT Performance Data Collection in OMS and PowerShell Script rule in my Operations Manager Management Group that I have connected to OMS. The key solution to make this happen are the “Sample Management Pack – Wizard to Create PowerShell script Collection Rules” described in this blog article http://blogs.msdn.com/b/wei_out_there_with_system_center/archive/2015/09/29/oms-collecting-nrt-performance-data-from-an-opsmgr-powershell-script-collection-rule-created-from-a-wizard.aspx.

With this solution I can practically get any data I want into OMS via SCOM and PowerShell Script, so I will start my solution for bringing in Service Manager Stats by defining some PowerShell commands to get the values I want. For that I will use the SMLets PowerShell Module for Service Manager.

For this blog article, I will focus on Incident Stats from SCSM. In a later article I will get in some more SCSM data to OMS.

Getting Ready

I have to do some preparations first. This includes:

  1. Importing the “Sample Management Pack – Wizard to Create PowerShell script Collection Rules”
    1. This can be downloaded from Technet Gallery at https://gallery.technet.microsoft.com/Sample-Management-Pack-e48040f7
  2. Install the SMLets for SCSM PowerShell Module (at the chosen Target server for the PowerShell Script Rule).
    1. I chose to install it from the PowerShell Gallery at https://www.powershellgallery.com/packages/SMLets
    2. If you are running Windows Server 2012 R2, which I am, follow the instructions here to support the PowerShellGet module, https://www.powershellgallery.com/GettingStarted?section=Get%20Started.
  3. Choose Target for where to run the SCSM commands from
    1. Regarding the SMLets and where to install, I decided to use the SCOM Root Management Server Emulator. This server will then run the SCSM commands against the Service Manager Management Server.
  4. Choose account for Run As Profile
    1. I also needed to think about the run as account the SCSM commands will run under. As we will see later the PowerShell Script Rules will be set up with a Default Run As Profile.
    2. The Default Run As Profile for the RMS Emulator will be the Management Server Action Account, if I had chosen another Rule Target the Default Run As Profile would be the Local System Account.
    3. Alternatively, I could have created a custom Run As Profile with a SCSM user account that have permissions to read the required data from SCSM, and configured the PowerShell Script rules to use that.
    4. I decided to go with the Management Server Action Account, and make sure that this account is mapped to a Role in SCSM with access to the work items I want to query against, any Operator Role will do but you could chose to scope and restrict more if needed:

At this point I’m ready for the next step, which is to create some PowerShell commands for the Script Rule in SCOM.

Creating the PowerShell Command Script for getting SCSM data

First I needed to think about what kind of Incident data I wanted to get from SCSM. I decided to get the following values:

  • Active Incidents
  • Pending Incidents
  • Resolved Incidents
  • Closed Incidents
  • Incidents Opened Last Day
  • Incidents Opened Last Hour

These values will be retrieved by the Get-SCSMIncident cmdlet in SMLets, using different criteria. The complete script is shown below:

# Debug file
$debuglog = $env:TEMP+\powershell_perf_collect_debug.log

Date | Out-File $debuglog

Who Am I: | Out-File $debuglog -Append
whoami |
Out-File $debuglog -Append

$ErrorActionPreference = Stop

Try {

Import-Module C:\Program Files\WindowsPowerShell\Modules\SMLets

$API = new-object -comObject MOM.ScriptAPI

$scsmserver = MY-SCSM-MANAGEMENTSERVER-HERE

$beforetime = $(Get-Date).AddHours(1)
$beforedate = $(Get-Date).AddDays(1)

$PropertyBags[email protected]()

$activeincidents = 0
$activeincidents = @(Get-SCSMIncident -ComputerName $scsmserver -Status Active).Count
$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Active Incidents)
$PropertyBag.AddValue(Value, [UInt32]$activeincidents)
$PropertyBags += $PropertyBag

Active Incidents: | Out-File $debuglog -Append
$activeincidents | Out-File $debuglog -Append

$pendingincidents = 0
$pendingincidents = @(Get-SCSMIncident -ComputerName $scsmserver -Status Pending).Count
$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Pending Incidents)
$PropertyBag.AddValue(Value, [UInt32]$pendingincidents)
$PropertyBags += $PropertyBag

Pending Incidents: | Out-File $debuglog -Append
$pendingincidents | Out-File $debuglog -Append

$resolvedincidents = 0
$resolvedincidents = @(Get-SCSMIncident -ComputerName $scsmserver -Status Resolved).Count
$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Resolved Incidents)
$PropertyBag.AddValue(Value, [UInt32]$resolvedincidents)
$PropertyBags += $PropertyBag

Resolved Incidents: | Out-File $debuglog -Append
$resolvedincidents | Out-File $debuglog -Append

$closedincidents = 0
$closedincidents = @(Get-SCSMIncident -ComputerName $scsmserver -Status Closed).Count
$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Closed Incidents)
$PropertyBag.AddValue(Value, [UInt32]$closedincidents)
$PropertyBags += $PropertyBag

Closed Incidents: | Out-File $debuglog -Append
$closedincidents | Out-File $debuglog -Append

$incidentsopenedlasthour = 0
$incidentsopenedlasthour = @(Get-SCSMIncident -CreatedAfter $beforetime -ComputerName $scsmserver).Count
$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Incidents Opened Last Hour)
$PropertyBag.AddValue(Value, [UInt32]$incidentsopenedlasthour)
$PropertyBags += $PropertyBag

Incidents Opened Last Hour: | Out-File $debuglog -Append
$incidentsopenedlasthour | Out-File $debuglog -Append

$incidentsopenedlastday = 0
$incidentsopenedlastday = @(Get-SCSMIncident -CreatedAfter $beforedate -ComputerName $scsmserver).Count
$PropertyBag = $API.CreatePropertyBag()
$PropertyBag.AddValue(Instance, Incidents Opened Last Day)
$PropertyBag.AddValue(Value, [UInt32]$incidentsopenedlastday)
$PropertyBags += $PropertyBag

Incidents Opened Last Day: | Out-File $debuglog -Append
$incidentsopenedlastday | Out-File $debuglog -Append

$PropertyBags

} Catch {

Error Catched: | Out-File $debuglog -Append
$(
$_.Exception.GetType().FullName) | Out-File $debuglog -Append
$(
$_.Exception.Message) | Out-File $debuglog -Append

}

Some comments about the script. I usually like to include some debug logging in the script when I develop the solution. This way I can keep track of what happens underway in the script or get some exceptions if the script command fails. Be aware though that doing $ErrorActionPreference=Stop will end the script if any errors, so it might be an idea to remove the debug logging when confirmed that everything works.

In the next part I’m ready to create the PowerShell Script Rule.

Creating the PowerShell Script Rule

In the Operations Console, under Authoring, create a new PowerShell Script Rule as shown below:

  1. Select the PowerShell Script (Performance – OMS Bound) Rule:I have created a custom destination management pack for this script.
  2. Specifying a Rule name and Rule Category: Performance Collection. As mentioned earlier in this article the Rule target will be the Root Management Server Emulator:
  3. Selecting to run the script every 30 minutes, and at which time the interval will start from:
  4. Selecting a name for the script file and timeout, and entering the complete script as shown earlier:
  5. For the Performance Mapping information, the Object name must be in the \\FQDN\YourObjectName format. For FQDN I used the Target variable for PrincipalName, and for the Object Name ServiceManagerIncidentStats, and adding the “\\” at the start and “\” between: \\$Target/Host/Property[Type=”MicrosoftWindowsLibrary7585010!Microsoft.Windows.Computer”]/PrincipalName$\ServiceMgrIncidentStats I specified the Counter name as “Service Manager Incident Stats”, and the Instance and Value are specified as $Data/Property(@Name=’Instance’)$ and $Data/Property(@Name=Value)$. These reflect the PropertyBag instance and value created in the PowerShell script:
  6. After finishing the Create Rule Wizard, two new rules are created, which you can find by scoping to the Root Management Server Emulator I chose as target. Both Rules must be enabled, as they are not enabled by default:
  7. Looking into the Properties of the rules, we can make edits to the PowerShell script, and verify that the Run as profile is the Default. This is where I would change the profile if I wanted to create my own custom profile and run as account for it.

At this point we are finished configuring the SCOM side, and can wait for some hours to see that data are actually coming into my OMS workspace.

Looking at Service Manager Performance Data in OMS

After a while I will start seeing Performance Data coming into OMS with the specified Object and Counter Name, and for the different instances and values.

In Log Search, I can specify Type=Perf ObjectName=ServiceMgrIncidentStats, and I will find the Results and Metrics for the specified time frame.

I can now look at Service Manager stats as performance data, showing different scenarios like how many active, pending, resolved and closed incidents there are over a time period. I can also look at how many incidents are created by each hour or by each day.

Finally, I can also create saved searches and alerts that creates for example alerts when the number of incidents for any counters are over a set value.

Thanks for reading, and look for more blog posts on OMS and getting SCSM data via NRT Perfomance Collection in the future 😉

Getting SCSM Object History using PowerShell and SDK

Some objects in the Service Manager CMDB, does not have the History tab. For example for Active Directory Users:

image

This makes it more difficult to keep track of changes made by either the AD Connector or Relationship Changes.

Some years ago this blog post from Technet showed how you could access object history programmatically using the SDK and a Console Application: http://blogs.technet.com/b/servicemanager/archive/2011/09/16/accessing-object-history-programmatically-using-the-sdk.aspx

I thought I could accomplish the same using PowerShell and the SDK instead, so I wrote the following script based on the mentioned blog article:

# Import module for Service Manager PowerShell CmdLets
$SMDIR    = (Get-ItemProperty 'hklm:/software/microsoft/System Center/2010/Service Manager/Setup').InstallDirectory
Set-Location -Path $SMDIR
If (!(Get-Module –Name System.Center.Service.Manager)) { Import-Module ".\Powershell\System.Center.Service.Manager.psd1" }

# Connect to Management Server
$EMG = New-Object Microsoft.EnterpriseManagement.EnterpriseManagementGroup "localhost"

# Specify Object Class
# In this example AD User
$aduserclass = Get-SCSMClass -DisplayName "Active Directory User"

# Get Instance of Class
$aduser = Get-SCSMClassInstance -Class $aduserclass | Where {$_.UserName -eq 'myusername'}

# Get History of Object Changes
$listhistory = $emg.EntityObjects.GetObjectHistoryTransactions($aduser)

# Loop History and Output to Console
ForEach ($emoht in $listhistory) {

    Write-Host "*************************************************************" -ForegroundColor Cyan
    Write-Host $emoht.DateOccurred `t $emoht.ConnectorDisplayName `t $emoht.UserName -ForegroundColor Cyan
    Write-Host "*************************************************************" -ForegroundColor Cyan

    ForEach ($emoh in $emoht.ObjectHistory) {

        If ($emoh.Values.ClassHistory.Count -gt 0) {
        
            Write-Host "*************************" -ForegroundColor Yellow
            Write-Host "Property Value Changes"  -ForegroundColor Yellow
            Write-Host "*************************"  -ForegroundColor Yellow

            ForEach ($emoch in $emoh.Values.ClassHistory) {
                ForEach ($propertyChange in $emoch.PropertyChanges) {
                    $propertyChange.GetEnumerator() | % {
                        Write-Host $emoch.ChangeType `t $_.Key `t $_.Value.First `t $_.Value.Second -ForegroundColor Yellow
                    }
                }
            }

        }
        
        If ($emoh.Values.RelationshipHistory.Count -gt 0) {

            Write-Host "*************************" -ForegroundColor Green
            Write-Host "Relationship Changes" -ForegroundColor Green
            Write-Host "*************************" -ForegroundColor Green

            ForEach ($emorh in $emoh.Values.RelationshipHistory) {

                $mpr = $emg.EntityTypes.GetRelationshipClass($emorh.ManagementPackRelationshipTypeId)
                Write-Host $mpr.DisplayName `t $emorh.ChangeType `t (Get-SCSMClassInstance -Id $emorh.SourceObjectId).DisplayName `t (Get-SCSMClassInstance -Id $emorh.TargetObjectId).DisplayName -ForegroundColor Green
            
            }

        }

    }

}

 

Running the script outputs the source of the changes, and any Property or Relationship changes, as shown in the below sample image:

image