Presenting at M365 Twin Cities 2023

After a few years off from community speaking engagements I’m excited to return to speak at M365 Twin Cities (formerly SharePoint Saturday Twin Cities, now with expanded scope and topics) on Jan 21, 2023. I’ve spoken at this conference twice before and have been very impressed with the level of organization, attendee engagement, and content all-up. I’ll be presenting the following 2 sessions. Registration is still open. If you are in the area, please sign up and look forward to seeing you there.

M365 Twin Cities
https://www.m365tc.com/

Registration
https://www.eventbrite.com/e/m365-twin-cities-winter-2023-tickets-471566266397

Title: Getting Up to Speed with Microsoft Graph Development

Description: “I hear that I need to use Microsoft Graph for developing against Microsoft 365 but I have no clue where to start.” “I want to grant access to company data without throwing in the entire kitchen sink.” Fear not fellow developers and admins. This session we will ramp you up to a 200 level knowledge on the pertinent parts of Microsoft Graph including endpoints available, syntax, authentication flows, and more. We will also cover useful examples of what can be accomplished using these APIs. Prior experience with Microsoft Graph is not required but can be helpful.

Title: How to Use Power Automate and Microsoft Graph in Daily Work

Description: Do you need to automate parts of your daily work routine? What Microsoft Cloud data is available through Microsoft Graph for automating? In this session we’ll walk through multiple real world examples of using Power Automate for daily tasks such as assigning round robin tasks to team members, sending weekly reminders, processing survey results, and more. Prior experience with Power Automate is useful but not required. Target audience is open to all (productivity workers, developers, admins, etc.)

-Frog Out

Tips on Email Signatures

In this post I’ll share tips and questions to ask yourself when creating an email signature. The instructions will be specific to Outlook, but the concepts apply to any email program.

Background

This week a peer of mine asked me:

“Do you have any tips or advice for creating an email signature? I interact with external customers quite a bit and would like to appear more professional over email.”

For those of you who are not familiar with email signatures, you can create a templatized signature at the bottom of your emails to include text, images, your electronic business card, a logo, and more. You can set up Outlook (or your favorite email client of choice) so that signatures are automatically added to all outgoing messages (replies and / or new messages) or add it to emails manually on a case-by-case basis.

Questions to ask yourself

Before you create your signature, take some time to think about who is the audience that will be reading your email signature. What things might they want to know about you, the work you do, how to find you (calendar, blog, etc.), etc.? The following are a series of items you may want to consider:

  • LinkedIn profile link
  • GitHub profile link
  • personal / work blog link
  • links to documentation or projects you work on
  • link to schedule a meeting with you (could be your work calendar availability or a scheduling service)
  • email address for sharing feedback (ex. manager, group alias, etc.)
  • upcoming OOF (you need to update regularly)
  • time zone / working hours
  • inspirational quote
  • …and more

An additional thought is to look at signatures that you can seen from other people and ask them if it is ok to borrow / copy / adapt it. I’ve done this many times in the past and my current signature was adapted from a colleague at Microsoft.

Example

The following is my current signature as an example:

Brian T. Jackett
Sr. Program Manager | Microsoft Graph CPx
Blog: BrianTJackett.com | Microsoft Teams (Chat) | Calendar availability (EST/EDT)
If you have any feedback about my work, please share with me or my manager, <redacted>.

How to add your email signature to Outlook

  • Copy your email signature
  • Open a new email message in Outlook
  • On the Message menu, select Signatures, then New, and type in a nickname for this signature
  • Paste in your new signature
  • Select OK to save and exit the email signature editor

Detailed instructions:​

Conclusion

Hopefully this post gives you some inspiration on what you would like to include. If you have suggestions or would like to share your own signature(s) please post in the comments.

-Frog Out

Outlook Calendar Tips for Remote Teams

As mentioned in my last blog post A New Role with Microsoft Graph Team, I mentioned I am joining the Microsoft Graph team. One of the nice aspects of our team is that we are diverse and globally dispersed. With the different time zones that our team all reside in I thought it would be helpful to review a few of my calendar settings in Outlook desktop and Outlook on the Web to help with scheduling meetings or calls.

<Update 2020-11-05>Update screenshots for meeting duration to include new option for end early or start late. Thanks to my teammate Glenn Block for the suggestion.</Update>

Working / Meeting Hours

Set your working / meeting days and hours so that teammates will know when you are generally available for scheduled meetings or calls. Personally, I wake up early most days and hence my start of the day is likely earlier than some others.

Outlook desktop: File -> Options -> Calendar -> Work time

Outlook calendar settings for working hours

Outlook on the Web: Settings -> Calendar -> View -> Meeting hours

Outlook on the Web calendar settings for meeting hours

Time Zones

Since my team is all over the world, it is important to be aware of time zones for scheduling meetings. In Outlook desktop it is possible to set your primary time zone and display 2 additional time zones. In the following screenshot I have set Eastern Time (US & Canada) as my primary time zone with additional time zones for Pacific Time (US & Canada) and East Africa Time (Nairobi). I have purposely kept the labels short so that they fit easily in the display on calendar views.

Outlook desktop: File -> Options -> Calendar -> Time zones

I have only been able to add a single time zone in Outlook on the Web. If someone knows a way to add multiple please let me know in the comments or contact me.

Outlook on the Web: Settings -> Calendar -> Language and time

End Meetings Early / Start Meetings Late

Whether you are hosting a meeting in-person or online there are many reasons you may want to end your meeting early or start your meeting late including:

  • Allow attendees time to walk to their next meeting room
  • Encourage attendees to wrap up their meeting without overlapping the following time block
  • Give attendees time for a mental break / chance to use the restroom in between meetings
  • …and more

Outlook desktop: File -> Options -> Calendar -> Calendar options

Outlook on the Web: Settings -> Calendar -> Events and invitations -> Shorten duration for all events

Share Free / Busy Times

In addition to setting your working / meeting hours, you can also come to show your free / busy times (and more) with other people.

Outlook desktop: File -> Options -> Calendar -> Calendar options -> Free/Busy Options

By default you may see that all users in your organization are able to see your free / busy times. You can adjust permission levels to show more details or add / remove additional people to have access to view your calendar.

Outlook on the Web: Settings -> Calendar -> Shared calendars

Publish Calendar

Aside from showing your free / busy times to people internal to your organization, sometimes you may want to publish your calendar to people external to your organization. Currently I have found this easiest to do through Outlook on the Web.

Outlook on the Web: Settings -> Calendar Shared calendars -> Publish a calendar

After you publish your desired calendar you can provide people with either an HTML (render in browser) or ICS (universal calendar file format) link.

Conclusion

In this post I walked through a number of calendar settings and preferences on my Outlook desktop and Outlook on the Web client. I hope this helps you to think of the diverse and global audience that you may be working with currently or in the future. If you have any additional tips you recommend please share them in the comments.

-Frog Out

PowerShell Script to Create Office 365 Security and Compliance Center eDiscovery Case and Holds

This week my customer and a peer both asked for a sample PowerShell script to automate the creation of an Office 365 Security and Compliance Center eDisovery case, hold, and content search.  This post will share out that script and a few things to be aware of (ex. deprecating basic authentication) that are important.

Background

The below script accomplishes the following tasks:

  • Create a Security and Compliance Center eDiscovery case
  • Place an in-place hold on multiple users’ Exchange Online mailboxes
  • Create a content search within eDiscovery case for any folders named “Legal Hold” and the child folders under them

Important Note

As of the publish date (Mar 4th, 2020) the Security and Compliance Center remote PowerShell module relies on basic authentication.  The Exchange team has publicly shared that basic authentication for Exchange Online will be deprecated by Oct 2020.  As such that means the below script may not be usable in its current form in ~6 months.  When a replacement or update is available I will attempt to update this sample to reflect that.

Exchange Online deprecating Basic Authentication
https://support.microsoft.com/en-us/help/4521831/exchange-online-deprecating-basic-auth

In terms of the Exchange Online remote PowerShell module there is a v2 module being developed (active development, not ready for production) which you can find on the PowerShell Gallery.  This new module support OAuth authentication which resolves the issue of deprecated basic authentication.

ExchangeOnlineManagement module on PSGallery
https://www.powershellgallery.com/packages/ExchangeOnlineManagement

Solution

Before running this script, ensure that the account you log in with has the appropriate permissions to both Exchange Online as well as Security and Compliance Center.  My sample uses a single admin account but you may adapt the script to use separate accounts if needed.  See the following articles for more details.

Connect to Exchange Online PowerShell
https://docs.microsoft.com/en-us/powershell/exchange/exchange-online/connect-to-exchange-online-powershell/connect-to-exchange-online-powershell?view=exchange-ps

Connect to Office 365 Security & Compliance Center PowerShell
https://docs.microsoft.com/en-us/powershell/exchange/office-365-scc/connect-to-scc-powershell/connect-to-scc-powershell?view=exchange-ps

Note: If you do not see the below Gist please refer to code at this location: EXO_New-SCCeDiscoveryCaseAndHold.ps1


Set-StrictMode Version "Latest"
# eDiscovery case creation
$caseName = 'Smith v. Johnson';
$UPN = 'user1@contoso.onmicrosoft.com', 'user2@contoso.onmicrosoft.com'
$description = "$caseName"
$policyName = "$caseName – Hold Policy"
$ruleName = "$caseName – Hold Rule"
$searchName = "$caseName – Search Name"
$rootFolderNameQuery = "Legal Hold"
function GetFolderQueries {
param (
[string]
$rootFolderNameQuery,
[string[]]
$UPN
)
$folderQueries = @()
foreach($user in $UPN)
{
$rootFolderStats = Get-MailboxFolderStatistics Identity $user | Where-Object name -eq $rootFolderNameQuery
$childFolderStats = Get-MailboxFolderStatistics Identity $user | Where-Object FolderPath -like "$($rootFolderStats.FolderPath)*"
# sample script to convert folderId: https://docs.microsoft.com/en-us/microsoft-365/compliance/use-content-search-for-targeted-collections?view=o365-worldwide#step-1-run-the-script-to-get-a-list-of-folders-for-a-mailbox-or-site
foreach ($folderStatistic in $childFolderStats)
{
$folderId = $folderStatistic.FolderId;
$folderPath = $folderStatistic.FolderPath;
$encoding= [System.Text.Encoding]::GetEncoding("us-ascii")
$nibbler= $encoding.GetBytes("0123456789ABCDEF");
$folderIdBytes = [Convert]::FromBase64String($folderId);
$indexIdBytes = New-Object byte[] 48;
$indexIdIdx=0;
$folderIdBytes | Select-Object skip 23 First 24 | %{$indexIdBytes[$indexIdIdx++]=$nibbler[$_ -shr 4];$indexIdBytes[$indexIdIdx++]=$nibbler[$_ -band 0xF]}
$folderQuery = "folderid:$($encoding.GetString($indexIdBytes))";
$folderStat = New-Object PSObject
Add-Member InputObject $folderStat MemberType NoteProperty Name UPN Value $user
Add-Member InputObject $folderStat MemberType NoteProperty Name FolderPath Value $folderPath
Add-Member InputObject $folderStat MemberType NoteProperty Name FolderQuery Value $folderQuery
$folderQueries += $folderStat
}
}
return $folderQueries
}
# Connection to EXO and SCC PowerShell Modules
$UserCredential = Get-Credential
$Session = New-PSSession ConfigurationName Microsoft.Exchange ConnectionUri https://outlook.office365.com/powershellliveid/ Credential $UserCredential Authentication Basic AllowRedirection
Import-PSSession $Session AllowClobber
$SccSession = New-PSSession ConfigurationName Microsoft.Exchange ConnectionUri https://ps.compliance.protection.outlook.com/powershellliveid Credential $UserCredential Authentication Basic AllowRedirection
Import-PSSession $SccSession AllowClobber DisableNameChecking
# Create eDiscovery case, hold, and compliance search
New-ComplianceCase Name $caseName Description $description
New-CaseHoldPolicy Name $policyName Case $caseName ExchangeLocation $UPN Enabled $true
New-CaseHoldRule Name $ruleName Policy $policyName Disabled $false
$folderQueries = GetFolderQueries rootFolderNameQuery $rootFolderNameQuery UPN $UPN
New-ComplianceSearch Name $searchName Case $caseName HoldNames "All" ContentMatchQuery $folderQueries.FolderQuery

Conclusion

In this post I shared a sample script for automating the creation of an Office 365 Security and Compliance Center eDiscovery case, hold, and folder scoped content search.  The folder scoping was an interesting detour as I had to track down the way to gather folder IDs from a product group engineer sample (linked in the above sample).  I hope you find this useful and good luck scripting.

-Frog Out

Presenting at Collab365 SharePoint Summit 2019

I have the privilege of presenting “Getting Started with Microsoft Graph Development” at the upcoming Collab365 SharePoint Summit taking place Sept 10-12, 2019.  This is a free online conference with MVPs and experts from around the world presenting on developer, IT Pro, and adoption topics.

Title: Getting Started with Microsoft Graph Development

Abstract: “I hear that I need to use Microsoft Graph for developing against Office 365 but I have no clue where to start.” “I want to grant access to company data without throwing in the entire kitchen sink.” Fear not fellow developers and admins.

This session we will ramp you up to a 200 level knowledge on the pertinent parts of Microsoft Graph including endpoints available, syntax, authentication flows, and more. We will also cover useful examples of what can be accomplished using these APIs. Prior experience with Microsoft Graph is not required but can be helpful.

You can also purchase an all-access pass which includes lifetime access to the videos, additional e-books, and more.  Looking forward to participating in this great event.

Automate Creation of Azure AD Application with OAuth Permissions

<Update 2018-02-06>Updated with snippet to list out GUIDs for app roles that can be assigned.</Update>

In this post I will show how to automate the creation of an Azure AD Application and assign OAuth permissions to that application.  The latter part is tricky as there is not currently a PowerShell commandlet or Azure CLI command to assign OAuth permissions.  Instead we will leverage an authenticated call to the Microsoft Graph to assign the permissions.  For more in depth information about Azure AD apps, verifying the results, and more please see the following post which I am borrowing heavily from.  I had difficulty finding this information so this post is my attempt to spread the word and also add a few clarifications on the ADAL libraries used.

(Read this first!) Automating the creation of Azure AD Applications by Christer Ljung

http://www.redbaronofazure.com/?p=7197

Problem

Creating Azure AD apps typically involves logging into the Azure Portal (classic or “new” / Ibiza version) and manually clicking through multiple screens.  When developing a solution that needs to leverage Office 365 services (as is my case with a current project) it is helpful to automate the process of creating the Azure AD app and assigning the permissions.  If you happen to be assigning Admin permissions then additional steps will be required by an Azure AD domain administrator (see following screenshot).

AzureADApp1

Solution

Creating an Azure AD application can be accomplished in 2 lines of PowerShell.  Login to Azure then create the app.


Login-AzureRmAccount

$aadapp = New-AzureRmADApplication -DisplayName "Some amazing app" -HomePage https://localhost:8081/ -IdentifierUris https://localhost:8081/

***BONUS***

If you want to create an app that uses certificate based authentication you can use the following PowerShell commandlets.

Note: The commandlets for creating and exporting a certificate require Windows 8 or higher.  There are workarounds for Windows 7 or similar OS.  Feel free to reach out if you are in that scenario.


$pwd = Read-Host -AsSecureString -Prompt "Enter certificate password"

# process for Windows 8+ type OS
$ssc = New-SelfSignedCertificate -CertStoreLocation cert:\localmachine\my -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
                           -Subject "cn=MySuperSpecialCert" -KeyDescription "Used to access Azure Resources" `
                           -NotBefore (Get-Date).AddDays(-1) -NotAfter (Get-Date).AddYears(1)

# Export cert to PFX - uploaded to Azure App Service
Export-PfxCertificate -cert cert:\localMachine\my\$($ssc.Thumbprint) -FilePath ExportedSpecialCertFile.pfx -Password $pwd –Force
$KeyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$certFile = Get-ChildItem –Path <path to certificate file>
$x509 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$x509.Import($certFile.FullName, $pwd, $KeyStorageFlags)
$certValue = [System.Convert]::ToBase64String($x509.GetRawCertData())

# should match our certificate entries above.
$validFrom = [System.DateTime]::Now.AddDays(-1)
$validTo = [System.DateTime]::Now.AddYears(1)

$aadapp = New-AzureRmADApplication –DisplayName "Some amazing app" -HomePage "https://localhost:8080/" `
                                   -IdentifierUris "https://localhost:8080/" -CertValue $certValue `
                                   -StartDate $validFrom -EndDate $validTo

The next step involves granting OAuth permissions to the recently created Azure AD app.  As of the writing of this blog (Feb 2, 2018) there is not a PowerShell commandlet nor Azure CLI command to assign those permissions.  There is however a way to use the Microsoft Graph to assign permissions.  This is an adapted version of Christer’s example that I referenced earlier and uses a local version of the Active Directory Authentication Library (ADAL) DLLs.  Currently these are at version 3.19.1.

ADAL NuGet package

https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/

Extract the following DLLs into the folder where you are executing other PowerShell commands:

  • Microsoft.IdentityModel.Clients.ActiveDirectory.dll
  • Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll

$Tenant = "<Office 365 tenant name, ex. Contoso>"
$aadTenant = "$Tenant.onmicrosoft.com"
$adminUser = "<admin account with access to authenticate against MS Graph>"

# load ADAL DLLs
$adal = ".\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = ".\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"

[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null

 $clientId = "1950a258-227b-4e31-a9cf-717495945fc2"  # Set well-known client ID for AzurePowerShell
  $redirectUri = "urn:ietf:wg:oauth:2.0:oob" # Set redirect URI for Azure PowerShell
  $resourceAppIdURI = "https://graph.windows.net/" # resource we want to use
  $adminUserId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($adminUser, "OptionalDisplayableId")

 # Create Authentication Context tied to Azure AD Tenant
  $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority

  # Acquire token
  $authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, [Uri]$redirectUri, [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always, $adminUserId)

 $authHeader = $authResult.CreateAuthorizationHeader()
  $headers = @{"Authorization" = $authHeader; "Content-Type"="application/json"}   

# make call against MS Graph to apply OAuth permissions
$url = "https://graph.windows.net/$aadTenant/applications/$($aadapp.ObjectID)?api-version=1.6"
$postData = "{'requiredResourceAccess':[
     {'resourceAppId':'00000003-0000-0ff1-ce00-000000000000','resourceAccess':[{'id':'fbcd29d2-fcca-4405-aded-518d457caae4','type':'Role'}]},
     {'resourceAppId':'00000002-0000-0000-c000-000000000000','resourceAccess':[{'id':'311a71cc-e848-46a1-bdf8-97ff7156d8e6','type':'Scope'}]}
     ]}";
$result = Invoke-RestMethod -Uri $url -Method "PATCH" -Headers $headers -Body $postData  

Note the use of specific resoureAppId and resourceAccess values above.  These two examples grant the “read and write all items in SharePoint Online” admin consent permission and the default “read user profile data” delegated permission respectively.  In order to find out the GUIDs you may need you’ll need to add the permissions through the Azure portal UI, check the manifest file, and extract the GUIDs.  See Christer’s post for more details.

<Update 2018-02-06>  I recently found out it is possible to list out the Application role permissions and GUIDs needed above by running the following PowerShell against the Azure AD module (I’m using Azure AD “V2” Preview module, haven’t verified against the existing V1 module).

Connect-AzureAD
# 00000003-0000-0ff1-ce00-000000000000 is the AppId for SharePoint Online, call Get-AzureADServicePrincipal by itself to find other AppIds
$SPOApi = Get-AzureADServicePrincipal -Filter "AppId eq '00000003-0000-0ff1-ce00-000000000000'"
$SPOApi.AppRoles

</Update>

If you happen to assign an admin consent permissions (such as the “read and write all items in SharePoint Online” permission) an Azure AD domain administrator will still need to consent to that permission by clicking “Grant permission” inside the Azure portal.  I’m not aware of a way to automate that process but if you do know please share in the comments below.

Conclusion

Originally I had hoped automating creation of an Azure AD app would be a simple process.  Creation of the Azure AD app is easy, but adding certificate authentication and / or assigning OAuth permissions adds extra work to be done.  As seen in this post though much of that can be automated.  Hopefully this post saves you time and effort.  Feel free to leave any feedback or questions in the comments below.

-Frog Out