Tag Archives: Office 365

User photos in Office 365

The user photo story in Office 365 is not so straight forward. Photos are stored in Active Directory (AD) on-premises, Azure Active Directory (AAD), Exchange Online (EXO), SharePoint Online (SPO), and at first appearances possibly elsewhere as well (where does my Delve profile picture live, what about my Skype for Business (SfB) avatar?).

I have put together a flow diagram to represent how this actually works. It aims to demonstrate where user photos are stored and where different applications fetch user photos from (if they don’t store the images), and leads to some recommendations about user photo synchronisation.

Please note the date of this article (August 2016) and be conscious that Office 365 is changing rapidly and the following recommendations may have changed (e.g. Prior to the Delve user profile page, the SharePoint user profile page referenced images stored in SharePoint rather than Exchange. Changes such as these will continue to evolve).

User photos: the diagram

User photos flow in Office 365
User photos flow in Office 365

Where applications store and fetch user photos

Photo Location

Comments

Size

Is source?

On-premises AD DS in the thumbnailPhoto attribute

100Kb maximum

Recommended to be

96×96 or 48×48

Yes

Azure AD in the thumbnailPhoto attribute

100Kb maximum

Usually synced from AD DS via Azure AD Connect

Recommended to be

96×96 or 48×48

No.

Sync from AD

Exchange Online as property of the mailbox

500Kb

Provided manually by users or a bulk import can be scripted if source photos can be located and named appropriately.

If not provided, Exchange will reference the AAD thumbnailPhoto in some instances but only if the thumbnailPhoto is less than 10Kb.

Does not sync back to AD

Recommended to be

648×648

Yes

SharePoint Online ‘User Photos’ library

Three renditions of the EXO photo are automatically created in SharePoint after upload to EXO.

It generally takes up to 72 hours to see changes to EXO photo here. Sometimes we see that a user must ‘touch’ their profile before the sync will be performed.

NOTE: Updating user profile photo via Delve profile is actually updating EXO profile photo and not performing any actions directly in SharePoint Online.

Small is 48 x 48,

Medium is 72 x 72,

Large changes depending on the source image but is always square. I have seen as small as 120 x 120 and as large as 300 x 300. PnP image upload solution uploads these as 200 x 200.

No.

Sync from EXO

Skype for Business

Does not store any images

Uses the high resolution Exchange image if available, otherwise uses the AD thumbnailPhoto

EXO image or AD thumbnailPhoto

No.

Read from EXO

Delve user profile

Does not store any images

Uses the high resolution Exchange image if available, otherwise uses the AD thumbnailPhoto

EXO image or AD thumbnailPhoto

No.

Read from EXO

Yammer

Also stores its own photo. Out of scope of this discussion for now.

Yes

Likely issues and resolutions

Issue

Resolution

Exchange Online user photo is low quality (and in turn so is the SPO photo and SfB photo)

The source image coming from AD was/is low quality.

EXO user photos can be updated by users individually or if high res source photos are available this import can be scripted.

Source images should be jpg of 648×648 (resizing and compression can also be scripted)

Exchange Online user photo is high quality but SfB photo is low quality

High resolution photos from Exchange will be used as long as both Exchange and Sfb/Lync are of new enough versions (2013 or greater) and SfB is configured to allow all photos (not just those from AD).

NB. If a user doesn’t have a mailbox (e.g. not licenced) then they will be displayed using the AD photo

There is no Exchange Online user photo (and in turn there is no SPO photo or SfB photo)

A photo has not been imported to the user’s EXO mailbox and the AAD thumbnailPhoto either doesn’t not contain an image or that image is greater than 10Kb.

Import of photos up to 500Kb to EXO mailbox can be scripted (the source images could be on a file share, or AAD).

Changes to user photos are reflected quickly in Exchange and Skype but take days to replicate to SPO

Exchange to SPO synchronisation is a periodic process and can take up to 72 hours.

A custom solution can perform this replication on demand (e.g. at the same time EXO user photos are set)

User photos changed in other systems which update AD are not reflected in EXO, SPO, SfB.

E.g. A user in an on-premises SharePoint farm updates their user photo

When AD is updated, it is synchronised with AAD but that is as far as it gets as the “sync” from AAD to EXO is one-off import rather than a Sync.

Unlikely to be desirable to create a custom sync relationship here as users will want to be able to update EXO directly and won’t want their photo’s overwritten

User photos updated in EXO aren’t replicated to other systems which share an AD.

E.g. An on-premises SharePoint farm

The user photo in EXO is not synched back to AD – it can’t be consistently as the AD thumbnailPhoto attribute only supports photos up to 100Kb where EXO supports larger images.

Potential for a custom solution to sync images back to AD after having resized/compressed them to <100kb – However general recommendation is that AD thumbnailPhoto optimal size is 10Kb and 96×96.

Recommendations

Use Exchange user photos as the master. Allow users to update their user photos but pre-populate their user photo if possible and before end users are provided any access to the system.

If high resolution photos are available, script import of high resolution photos (648×648) to Exchange Online (see Set-UserPhoto and this). These will then be visible in Exchange, in Skype, and, once processed, in SharePoint Online. In a dispersed environment this may have to be managed by many teams rather than trying to compile a single list of all user photos.

Users may then update their user profile photo directly via Outlook or indirectly via their Delve profile.

If synchronisation back to AD is required in order serve other applications (e.g. an on-premises SharePoint farm) then a custom solution could provide synchronisation from EXO to AD but this process should compress and shrink images as the recommended size of thumbnailPhoto images is only 96×96 and 10Kb.

Paul.


Azure AD app wildcard Reply URL

Azure AD apps (a.k.a Azure Active Directory apps, a.k.a AAD apps) are an essential component when interacting with Office 365 data outside of SharePoint – Mail, Calendar, Groups, etc.

As an O365 developer I have found myself writing JavaScript code against AAD apps (using ADAl.js) and often, especially during development, found myself entering a long list of Reply URLs. Reply URLs must be specified for any location from which authentication to AAD occurs. From a practical standpoint this results in someone (an Azure Administrator) having to update the list of Reply URLs every time a web part is inserted into a page or a new site is provisioned which relies on an Azure AD app.

If this is not done, the user is redirected to Azure login failure with ‘The reply address … does not match the reply addresses configured for the application’.

Error when Reply URL is not correctly specified
Error when Reply URL is not correctly specified

Perhaps the following is documented elsewhere but I have not come across it – a Reply URL can be specified using wildcards!

Using a wildcard Reply URL when configuring an AAD app
Using wildcard Reply URLs when configuring an AAD app

Probably the most common use for this is to end a Reply URL with an asterisk (wildcard) which will permit any URL which begins with the characters preceding it.

e.g. https://tenant.sharepoint.com/*
This example would support any URL coming from any page in SharePoint Online from within the named tenant.

It is also possible to use the wildcard character elsewhere in the Reply URL string.
e.g. https://*.sharepoint.com/*
This example would support any URL coming from any page in SharePoint Online from within *any* tenant.

Armed with this knowledge, be responsible and limit strictly how it is utilised. The implementation of Reply URL is a security feature and it is important that only trusted locations are allowed to interact with your app. I recommend only using wildcard Reply URLs in development environments.

Paul.

How to get your Office 365 Theme appearing immediately

With the addition of Office 365 themes we can provide some company branding throughout the Office 365 suite. But why does it take so long to propagate down throughout the SharePoint sites in the tenant?

Set an Office 365 theme
Set an Office 365 theme

These themes affect the suite bar across the top of the page. I see it often that after setting the Office 365 theme for a tenant, the theme gets applied to Mail, Calendar, Delve, etc but can seem to take a very long time before it appears on SharePoint sites.

Well it is actually just taking a long time to appear on SharePoint sites if you have visited SharePoint recently. The Office 365 theme colours are cached locally in the browser using local storage.

The local storage key that is used is: SPSuiteLinksJson. This appears to store everything that is required to render the entire suite bar.

Interestingly there is another local storage key which appears to store the date at which the suite bar data was cached: SPSuiteLinksDate. Presumably this is used to calculate when the cache should be refreshed from the server. I am not sure exactly how long this is yet. Please comment if you have figured out the cache timeout.

Regardless of its duration, if you want to force end users to get the latest theme more often you have the options of clearing either of these values or modifying the date field appropriately. But really, I can’t imagine why this would be so important although its nice to know that a bit of JavaScript can sort this out if need be.

Paul.

FYI – The value that is stored will be something like the following (taken from a test tenant):

{"SPSuiteVersion":2,"SPIsMobile":false,"CssUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/css/shellg2coremincss_40fd4be6.css","JsUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/JSC/CoreMinShellG2Bundle.js","NavBarData":{"AboutMeLink":{"BrandBarText":null,"Id":"ShellAboutMe","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"About me","Title":"Go to the My profile page","Url":"https://tenant-my.sharepoint.com/person.aspx"},"AdminLink":{"BrandBarText":null,"Id":"ShellAdmin","MenuName":"Admin centers","ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Admin","Title":"Go to the Office 365 admin center","Url":"https://portal.office.com/admin/default.aspx"},"AppsImage":null,"AppsLinks":null,"ClientData":"{\"AddBusinessUserUrl\":null,\"AdminHelpUrlFormat\":\"http:\\/\\/o15.officeredir.microsoft.com\\/r\\/rlidOfficeWebHelp?p1=O365ENTADMIN&clid=1033&ver=15&services=INTUNE_O365%2cYAMMER_ENTERPRISE%2cMCOSTANDARD%2cSHAREPOINTSTANDARD%2cEXCHANGE_S_STANDARD&p2=O365&HelpID={HelpID}\",\"AppHeaderLinkText\":null,\"AppHeaderLinkUrl\":null,\"AppLauncherErrorHelpUrl\":\"http:\\/\\/o15.officeredir.microsoft.com\\/r\\/rlidOfficeWebHelp?p1=O365ENTADMIN&clid=1033&ver=15&services=INTUNE_O365%2cYAMMER_ENTERPRISE%2cMCOSTANDARD%2cSHAREPOINTSTANDARD%2cEXCHANGE_S_STANDARD&p2=O365&HelpID=O365E_AppLTrustedSites\",\"AppSearchEnabled\":false,\"AppsCustomizationDisabled\":false,\"AppsDiscoverabilityDisabled\":false,\"AppsDragAndDropDisabled\":false,\"AppsGetAllAppTilesEnabled\":false,\"AppsPrePinnedDisabled\":false,\"AppsResizingDisabled\":true,\"AppsUpdateTimeSpan\":3600000,\"CDNUrl\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\",\"CardBundleJS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/JSC\\/CardBundle.js\",\"ChatNotificationsDisabled\":false,\"ClearUserThemeCacheUrls\":\"[{\\\"WorkloadId\\\":\\\"AdminPortal\\\",\\\"WorkloadUrl\\\":\\\"https:\\/\\/portal.office.com\\/misc\\/expirecookies.aspx\\\",\\\"RequestType\\\":\\\"img\\\",\\\"NoRequestInSameWorkload\\\":true},{\\\"WorkloadId\\\":\\\"Sharepoint\\\",\\\"WorkloadUrl\\\":\\\"https:\\/\\/tenant-my.sharepoint.com\\/_layouts\\/15\\/ClearLocalCache.aspx\\\",\\\"RequestType\\\":\\\"iframe\\\",\\\"NoRequestInSameWorkload\\\":false},{\\\"WorkloadId\\\":\\\"Sharepoint\\\",\\\"WorkloadUrl\\\":\\\"https:\\/\\/tenant.sharepoint.com\\/_layouts\\/15\\/ClearLocalCache.aspx\\\",\\\"RequestType\\\":\\\"iframe\\\",\\\"NoRequestInSameWorkload\\\":false}]\",\"ClientTelemetryEnabled\":false,\"ClientTelemetrySource\":\"O365SuiteUX\",\"ClientTelemetryToken\":\"\",\"ClientTelemetryUIVersion\":\"1000\\/1.6.5.0\",\"ClientTelemetryUrl\":\"\",\"CollectorIds\":[\"SharePoint\",\"Graph\",\"FirstParty\",\"LineOfBusiness\"],\"ConsumerWorkloadUrls\":null,\"DefaultSavedUserUrl\":null,\"DisplayNameFormat\":null,\"EditPhotoPopupUrl\":\"https:\\/\\/outlook.office365.com\\/ecp\\/PersonalSettings\\/EditAccount.aspx?chgPhoto=1&exsvurl=1&realm={0}\",\"ExchangeWLADUpdateInterval\":28800000,\"FlexPaneDisabled\":false,\"GallatinLegalAlertEnabled\":false,\"GroupsDisabled\":true,\"HasEXOLicense\":true,\"HasSkypeLicense\":true,\"IdentitySwitcherEnabled\":false,\"ImmersiveProfileUrl\":\"profile\\/\",\"ImmersiveSettingsUrl\":\"settings\\/\",\"IsConsumerShell\":false,\"IsGuestMode\":false,\"IsIWDelveLinkPresent\":true,\"IsNFDDisabled\":false,\"IsNFDOnO365SuiteServiceEnabled\":false,\"IsO365SuiteServiceEnabled\":false,\"IsPartnerResellerPage\":false,\"IsRTL\":false,\"IsTenantDirSyncEnabled\":false,\"LoadUserThemesUrl\":\"https:\\/\\/portal.office.com\\/data.theme?action=tu&l=en-US&tt=G2&cdnver=16.00.0915.008\",\"LocalNotificationsDisabled\":false,\"LogArgLength\":1024,\"LogLevelSwitches\":[false,true,true,true],\"LogLevelSwitchesForPage\":null,\"LogUrl\":\"https:\\/\\/clientlog.portal.office.com\\/l.l\\/\",\"LyncIntegrationDisabled\":true,\"LyncIntegrationUrl\":\"https:\\/\\/swx.cdn.skype.com\\/shared\\/v\\/1.2.3\\/SkypeBootstrap.min.js\",\"MePhotoCachingDisabled\":false,\"MobileShellDisabled\":true,\"MultipleAADSwitchingEnabled\":false,\"MyAccountEnabled\":false,\"MyAccountUrl\":\"account\\/\",\"MyAppsUrl\":\"https:\\/\\/portal.office.com\\/myapps\",\"NeutralGrayColors\":[\"000000\",\"212121\",\"333333\",\"666666\",\"767676\",\"A6A6A6\",\"C8C8C8\",\"EAEAEA\",\"F4F4F4\",\"F8F8F8\",\"FFFFFF\"],\"NeutralStatusColors\":[\"A80F22\",\"D83B01\",\"2A8DD4\",\"107C10\"],\"NewAppLauncherHelpUrl\":null,\"NewAppNotificationEnabled\":false,\"NewMailNotificationsDisabled\":false,\"NoResultsHelpLinkUrl\":\"https:\\/\\/support.office.com\",\"NotificationsFlexPaneEnabled\":true,\"NotificationsSettingsV2Enabled\":false,\"PUID\":\"1003BFFD91368966\",\"PortalUrl\":\"https:\\/\\/portal.office.com\\/\",\"ProfileAboutCardApiUrl\":\"api\\/profile\\/data\",\"ProfileDoughboyUrl\":\"\\/images\\/profile\\/doughboy.png\",\"ProfileHeaderBGUrl\":\"\\/images\\/profile\\/profile-header-bg.jpg\",\"ProfileOrgChartCardApiUrl\":\"api\\/profile\\/org\",\"ProfilePaneEnabled\":false,\"RemindersNotificationDisabled\":false,\"RenderAsyncDisabled\":false,\"ResponsiveShellEnabled\":false,\"SHSID\":\"0822f3b5-7e00-4401-8436-34dc280519a9\",\"SavedUserUrl\":null,\"SettingsCardApiUrl\":\"api\\/settings\\/cards\",\"SettingsFlexPaneEnabledWorkloads\":\"ShellAdmin;AdminPortal\",\"SettingsPaneEnabled\":false,\"SettingsSearchClassifierUrl\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/JS\\/SettingSearch\\/SettingsSearchClassifier_en.js\",\"SettingsSearchNlrtUrl\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/JS\\/SettingSearch\\/MicrosoftNaturalLanguageRuntime.js\",\"SettingsSessionStorageEnabled\":false,\"ShellCoreCSS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/css\\/shellg2corecss_9037d638.css\",\"ShellCoreCSSResourceKey\":\"shellg2corecss\",\"ShellCoreJS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/JSC\\/CorePrimeShellG2Bundle.js\",\"ShellCoreJSResourceKey\":\"shellcoreprimeg2m\",\"ShellPlusCSS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/css\\/shellg2pluscss_1db8cf27.css\",\"ShellPlusCSSResourceKey\":\"shellg2pluscss\",\"ShellPlusJS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/JSC\\/O365ShellG2Plus.js\",\"ShellPlusJSResourceKey\":\"shellplusg2m\",\"ShellPlusNarrowCSS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/css\\/shellg2plusncss_c867ccef.css\",\"ShellPlusNarrowCSSResourceKey\":\"shellg2plusncss\",\"ShellPlusNarrowJS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/JSC\\/O365ShellG2PlusN.js\",\"ShellPlusNarrowJSResourceKey\":\"shellplusg2n\",\"ShellPlusTouchCSS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/css\\/shellg2plustcss_846fbfc5.css\",\"ShellPlusTouchCSSResourceKey\":\"shellg2plustcss\",\"ShellPlusTouchJS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/JSC\\/O365ShellG2PlusT.js\",\"ShellPlusTouchJSResourceKey\":\"shellplusg2t\",\"ShellPlusWideCSS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/css\\/shellg2pluswcss_5c4da058.css\",\"ShellPlusWideCSSResourceKey\":\"shellg2pluswcss\",\"ShellPlusWideJS\":\"https:\\/\\/prod.msocdn.com\\/16.00.0915.008\\/en-US\\/JSC\\/O365ShellG2PlusW.js\",\"ShellPlusWideJSResourceKey\":\"shellplusg2w\",\"ShowMailNotificationCount\":false,\"ShowNewAppLauncher\":false,\"ShowNewMyAppsLink\":true,\"SkypeApiKey\":\"b32c0389-1143-4a42-b15f-7b860f9eafbb\",\"SocialNotificationsDisabled\":true,\"SuiteServiceCallsDisabled\":false,\"SuiteServiceUrl\":\"https:\\/\\/outlook.office365.com\\/owa\\/service.svc\",\"SwitchToBusinessUserUrl\":null,\"SystemNotificationsDisabled\":false,\"TID\":\"2fb81f0d-c6ef-4c83-b35c-9553261f9d9b\",\"TenantPrimaryColorShades\":[\"149B14\",\"18BD19\",\"18BD19\",\"1DE01D\",\"72D071\",\"C7E2C7\",\"FDFEFD\",\"FFFFFF\",\"FFFFFF\"],\"TenantThemeColors\":[\"1DE01D\",\"0CA597\",\"FFFFFF\",\"FFFFFF\"],\"TenantThemeCssUrl\":\"https:\\/\\/portal.office.com\\/data.theme?action=tc&tc=1DE01D|0CA597|FFFFFF|FFFFFF&tt=G2&tv=40357e1c-4ba4-4ff1-94ae-6df34f02fe07&l=en-US\",\"ThemeCssUrl\":\"https:\\/\\/portal.office.com\\/data.theme?action=tc&tc=1DE01D|0CA597|FFFFFF|FFFFFF&tt=G2&tv=40357e1c-4ba4-4ff1-94ae-6df34f02fe07&l=en-US\",\"ThemePanelEnabled\":true,\"UID\":\"f0f20750-f14a-416b-84da-03119aa4ac1e\",\"UPN\":\"ccdev5@tenant.onmicrosoft.com\",\"UserPersonalizationAllowed\":false,\"UserThemeId\":\"Base\",\"UserThemesPanelUrl\":null,\"UserThemesSettingsPageUrl\":\"https:\\/\\/portal.office.com\\/EditProfile15.aspx?serviceId=ThemeItem\",\"WorkloadId\":\"Sharepoint\"}","CommunityLink":{"BrandBarText":null,"Id":"ShellCommunity","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Community","Title":"Community","Url":"http://g.microsoftonline.com/0BX20en/142"},"CompanyDisplayName":"Content and Code","CorrelationID":"0822f3b5-7e00-4401-8436-34dc280519a9","CultureName":"en-US","CurrentMainLinkElementID":"ShellSharepoint","CurrentWorkloadHelpSubLinks":null,"CurrentWorkloadSettingsLink":null,"CurrentWorkloadSettingsSubLinks":null,"CurrentWorkloadUserSubLinks":null,"Dimensions":{"Top":50},"DownArrowImage":null,"DownWhiteArrowImage":null,"FeedbackLink":{"BrandBarText":null,"Id":"ShellFeedback","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Feedback","Title":null,"Url":"https://portal.office.com/SendSmile?wid=2"},"FlightName":"15GA,Exp180917IngestionFailedStatusTreat1,Exp130705C,Exp170859ihdc100,SE80186HRCSetupRedirect,SE130231CFRV2,SE130238GeminiSignupUI,SE160251CFRV2Detail,SE160252CFRV2Prefetch,SE160258ShellDelveLink,SE190296ShellVideoLink,SE190331GeminiPurchaseUI,SE190354IWSignupAlertsOn,SE190448AADUXChangePassword,SE190549NotificationsFlexPane,SE190532AggregatedServiceHealth,SE200626ShellSwayLink,SE200631PartnerFeedback,SE200681SwitchReadFromAPIService,SE220756PartnerClientTroubleshoot,SE220826AppStore,SE220829LOBApps,SE220825Office2016PreviewForPc,SE220822IntuneMDMStaged,SE240791PartnerUsageTracking,SE240809ShellServiceThemeCache,SE240810O365ImmersiveSettings,SE240827CortanaAccessSettings,SE240829ShellClassNoteBookLink,SE240843PartnerSettingsPageFeature,SE240844HelpFlexPane,SE240866MobileAdmin,SE240880OAuthAuthenticationForAPIService,SE240881GeminiCatalogUI,SE240883ShellNewMyAppsLink,SE250903CFRV3,SE250928ShellPowerBILink,SE260930UnifiedSetup,SE240865ShellStaffNoteBookLink,SE271003CFRV3CsvExportEnabled,SE271005CutoverProxy,SE271017SwitchReadFromAPIService,SE271018CFRV3TS,SE271014AdvSetupImap,SE281032AetherSubscriptionsUI,SE291071PartnerResellerMultiPartner,SE281034OfficeHome3,Exp9015C","FlipHelpIcon":false,"FooterCopyrightLogoTitle":null,"FooterCopyrightText":null,"FooterICPLink":null,"FooterLogoImage":null,"HasTenantBranding":true,"HelpImage":null,"HelpLink":{"BrandBarText":null,"Id":"HelpLink","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Help","Title":null,"Url":"&services=INTUNE_O365%2cYAMMER_ENTERPRISE%2cMCOSTANDARD%2cSHAREPOINTSTANDARD%2cEXCHANGE_S_STANDARD&p2=O365"},"ImageClusterUrl":null,"IsAuthenticated":true,"LegalLink":{"BrandBarText":null,"Id":"ShellLegal","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Legal","Title":"Legal","Url":"http://g.microsoftonline.com/0BX20en/721"},"LogoIconID":"o365logo","LogoImage":null,"LogoNavigationUrl":"https://portal.office.com/Home","LogoThemeableImage":null,"MenuTitleText":null,"MyProfileUrl":"https://portal.office.com/EditProfile15.aspx?ServiceID=LanguageItem","NavBarAriaLabel":null,"NotificationsBellIconImage":null,"NotificationsCountLabelText":null,"NotificationsHighIconImage":null,"NotificationsLowIconImage":null,"NotificationsMediumIconImage":null,"NotificationsPopupHeaderText":null,"NotificationsProgressIconImage":null,"O365SettingsLink":{"BrandBarText":null,"Id":"ShellO365Settings","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Office 365 settings","Title":null,"Url":"https://portal.office.com/settings/"},"PartnerLink":null,"PoweredByText":"powered by {0}","PrivacyLink":{"BrandBarText":null,"Id":"ShellPrivacy","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Privacy","Title":"Privacy","Url":"http://g.microsoftonline.com/0BX20en/138"},"SPO_MySiteHostUrl":null,"SPO_RootSiteUrl":null,"SessionID":"e88c267e-5b6e-49ab-8f2d-766510e25c46","SettingsImage":null,"SignOutLink":{"BrandBarText":null,"Id":"ShellSignout","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Sign out","Title":"Sign out of Office 365 and return to the Sign-in page","Url":"https://login.microsoftonline.com/logout.srf?ct=1443035468&rver=6.4.6456.0&lc=1033&id=271346"},"TenantBackgroundImageUrl":null,"TenantLogoNavigationUrl":null,"TenantLogoUrl":{"BrandBarText":null,"Id":null,"MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":null,"Title":"Content and Code","Url":"data:image/png;base64,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"},"ThemeColors":null,"TransparentImageUrl":null,"TruncatedUserDisplayName":null,"UpArrowImage":null,"UseSPOBehaviors":false,"UserDisplayName":"Content and Code Dev 5","WorkloadLinks":[{"BrandBarText":"Outlook","Id":"ShellMail","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Mail","Title":"Go to your email","Url":"https://outlook.office365.com/owa/?realm=tenant.onmicrosoft.com&exsvurl=1&ll-cc=1033&modurl=0"},{"BrandBarText":"Outlook","Id":"ShellCalendar","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Calendar","Title":"Go to your calendar","Url":"https://outlook.office365.com/owa/?realm=tenant.onmicrosoft.com&exsvurl=1&ll-cc=1033&modurl=1"},{"BrandBarText":"Outlook","Id":"ShellPeople","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"People","Title":"Go to People and contacts","Url":"https://outlook.office365.com/owa/?realm=tenant.onmicrosoft.com&exsvurl=1&ll-cc=1033&modurl=2"},{"BrandBarText":null,"Id":"ShellNewsfeed","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Newsfeed","Title":"Go to your Newsfeed","Url":"https://tenant-my.sharepoint.com/default.aspx"},{"BrandBarText":null,"Id":"ShellDocuments","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"OneDrive","Title":"Go to OneDrive for Business","Url":"https://tenant-my.sharepoint.com/personal/ccdev5_tenant_onmicrosoft_com/_layouts/15/start.aspx#/_layouts/15/onedrive.aspx"},{"BrandBarText":null,"Id":"ShellSites","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Sites","Title":"Go to team sites","Url":"https://tenant-my.sharepoint.com/personal/ccdev5_tenant_onmicrosoft_com/_layouts/15/start.aspx#/Social/Sites.aspx"},{"BrandBarText":"Outlook","Id":"ShellTasks","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Tasks","Title":"Go to Outlook Web App for Tasks","Url":"https://outlook.office365.com/owa/?realm=tenant.onmicrosoft.com&exsvurl=1&ll-cc=1033&modurl=3"},{"BrandBarText":null,"Id":"ShellOfficeGraph","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Delve","Title":"Go to Delve","Url":"https://tenant-my.sharepoint.com/_layouts/15/me.aspx?origin=shell"},{"BrandBarText":null,"Id":"ShellVideo","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":null,"Text":"Video","Title":"Go to Video for Office 365 to share videos","Url":"https://tenant.sharepoint.com/portals/hub/_layouts/15/videohome.aspx"},{"BrandBarText":null,"Id":"ShellWordOnline","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Word Online","Title":"Go to Word Online","Url":"https://office.live.com/start/Word.aspx?auth=2"},{"BrandBarText":null,"Id":"ShellExcelOnline","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Excel Online","Title":"Go to Excel Online","Url":"https://office.live.com/start/Excel.aspx?auth=2"},{"BrandBarText":null,"Id":"ShellPowerPointOnline","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"PowerPoint Online","Title":"Go to PowerPoint Online","Url":"https://office.live.com/start/PowerPoint.aspx?auth=2"},{"BrandBarText":null,"Id":"ShellOneNoteOnline","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"OneNote Online","Title":"Go to OneNote Online","Url":"https://www.onenote.com/notebooks?auth=2"},{"BrandBarText":null,"Id":"ShellSway","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Sway","Title":"Go to Sway","Url":"https://www.sway.com?auth_pvr=OrgId&auth_upn=ccdev5@tenant.onmicrosoft.com"},{"BrandBarText":null,"Id":"ShellOfficeStore","MenuName":null,"ServiceId":null,"SubLinks":null,"TargetWindow":"_blank","Text":"Office 365 Store","Title":null,"Url":"https://portal.office.com/store"}]},"SharedCSSTouchWideUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/css/shellg2corewcss_8b4fb7b2.css","SharedJSTouchWideUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/JSC/CoreShellG2BundleW.js","SharedCSSTouchNarrowUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/css/shellg2corencss_18d28bae.css","SharedJSTouchNarrowUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/JSC/CoreShellG2BundleN.js","SharedCSSTouchDeviceUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/css/shellg2coretcss_ba6efae3.css","SharedJSTouchDeviceUrl":"https://prod.msocdn.com/16.00.0915.008/en-US/JSC/CoreShellG2BundleT.js","TenantPrimaryColorShades":["149B14","18BD19","18BD19","1DE01D","72D071","C7E2C7","FDFEFD","FFFFFF","FFFFFF"],"UserThemePrimaryColorShades":["104A7D","0D62AA","106EBE","0078D7","2B88D8","71AFE5","C7E0F4","DEECF9","EFF6FC"],"UserPersonalizationAllowed":false,"ThemeVersion":"G2","ShellRequestId":"aadc309d-b069-3000-9493-98edca352038"}

Paul.

Calling the Office 365 Unified API from JavaScript using ADAL.js

The goal of this post is to provide very basic ‘hello world’ example of how to call the Office 365 Unified API (aka Graph API) using JavaScript and the ADAL.js library. This has recently become possible (May 2015) now that CORS is supported by the Office 365 APIs (most of the individual endpoints support it as well as the unified API).

The ADAL library simplifies the process of obtaining and caching the authentication tokens required to retrieve data from Office 365. It is possible to avoid the ADAL library and handle this yourself, although I would recommend doing so as a learning exercise only.

I failed to find a simple example of how to achieve this, my search results often filled with examples of calling the APIs from server-side code or else utilising the Angular.js framework. This example is based on a more complex example.

The following snippet will log to the browser console the results of a call the to files endpoint of the Office 365 unified API, which will return a JSON object containing information about the files in the current users’ OD4B.

Before it will work you must complete the following steps (as described in detail here):

  1. Register an Azure Active Directory App. Note that *every* Office 365 subscriptions comes with AAD and supports the creation of an app
  2. Associate the required ‘permissions to other services’, in this case ‘Read users files’ via the Office 365 Unified API
  3. Allow implicit flow

Not covered explicitly in the above article but also critical are the following steps:

  • Get the App’s Client ID and copy it into the snippet
The App Client ID
The App Client ID
  • Get the Azure Active Directory subscription ID and copy it into the snippet
The subscription ID in Azure Active Directory
The subscription ID in Azure Active Directory

Once the above steps have been completed, you can try out the snippet by embedding in a Script Editor web part, or you can run it externally to SharePoint as part of, say, a provider hosted app.

NOTE: I found that the call to files endpoint is failing for certain users. I am still unsure whether this is due to external vs internal users (is working for internal [.onmicrosoft.com] users) or whether it could be licencing issue. The /beta/me endpoint is working in all cases.

Paul.

GLOSSARY

CORS: Cross-Origin Resource Sharing
ADAL: Active Directly Authentication Library
OD4B: OneDrive 4 Business

Suite Bar logo is not displayed after applying theme

In SharePoint Online I have detected a bug that prevents the suite bar logo (configured in the o365 tenant admin center) from being rendered after applying a theme with a non-default colour scheme. The logo is not just hidden or not visible; the HTML is no longer rendered to the page at all.

NOTE: This issue has been raised with Microsoft and they have acknowledged it as a bug. They have told is that a fix is planned for June 2015 and should take a couple of weeks to reach all tenants.

Update 06/08/2015

I was advised last week that a fix this issue had officially started rolling out. Today I can confirm that one of our test tenants no longer exhibits this behavior! At the time of writing some of our tenants have not yet received the fix, but it is definitely rolling out and should be with you soon if not already.

When exactly is this occurring?

There is a colour scheme file that nominated as the default for each master page. Only when using this colour scheme in conjunction with the given master page will the logo be present. Specifically, only a colour scheme which is nominated as the default colour scheme will show the suite navigation logo.

This is unrelated to any customisation work and can be observed on any OOTB SharePoint Online publishing site.

The default colour scheme for a given master page is nominated within that master page’s preview file. The preview file is the one which sits alongside the master page in the master page gallery and has the same name name as the master page but with an extension of ‘.preview’ instead of ‘.master’ (e.g., seattle.preview). The first line of the contents of this file identifies the default colour scheme for the master page. When this colour scheme is applied, the theming engine assumes that the CSS is already themed appropriately and does *not* act.

Identifying the default colour scheme for a master page
Identifying the default colour scheme for a master page

I still want the logo on every page, what can we do?

  1. As I have said, a fix is on the way – just wait a few months!
  2. We can inject the logo using JavaScript (or CSS, but we were unable to achieve a CSS only solution which meets our responsive design requirements)
    • It is against best practice
    • This approach is brittle as the suite bar is a living, changing thing – Microsoft is updating it all the time and any changes they make will likely break this functionality.
    • The logo will no longer be configurable via tenant admin settings.
  3. We can create and use multiple master pages – one for every theme colour
    • Partially defeats the purpose of master pages which is to maintain all common pages elements in a single place.
    • Master pages may fall out of synch
    • Adds complexity for support

I want to reproduce this myself, how do I do it?

  1. Create an new site collection in a tenant that has a suite navigation logo configured, using the Publishing Site template
  2. Note that the logo is present
  3. Via Site Settings, “Change the look”
  4. Choose the “Office” composed look
    [Specifically, we want the seattle master page and colour pallette 001]
  5. Change the colour scheme to anything except the default
  6. Apply the look
  7. Note that the logo is no longer present
  8. Re-apply the theme with the default colour scheme and logo will reappear

 

Paul.

SharePoint Online remote authentication (and Doc upload)

The SharePoint REST API is touted as being the tool to provide inter-platform integration with SharePoint Online. However, outside of .NET the authentication piece is not so straightforward. App authentication solves this issue for registered apps but I want to show how remote user authentication can be achieved, regardless of platform.

The goal of this post is to provide examples of the HTTP requests which need to be made in order to authenticate SharePoint Online. It then provides an example of using the same technique to upload a document and update metadata just to prove it all works 🙂

The type of applications where this kind of approach may be necessary include: a Java application, a PHP application, or JavaScript application where there is otherwise no SharePoint Online authentication context and the decision has been made (for whatever reason) that user authentication is most appropriate (as opposed to app authentication).

Edit: This approach will not work in a JavaScript environment due to cross-domain restrictions enforced by browsers (unless of course you are on the same domain, in which case you don’t need to worry about any of this anyway). The ADAL.js library is available for the cross-domain JS scenario. I have posted an example here: http://paulryan.com.au/2015/unified-api-adal/

I wrote about using the SharePoint REST API here (and background here, and here).

I will be providing examples of the requests using the ‘Advanced REST Client’ Google Chrome extension.

Authenticate

The authentication piece comes in a few steps:

  • Get the security token
  • Get the access token
  • Get the request digest

Get the security token

First we must provide a username and password of a user with Contribute access to the Roster Data library and the URL at which we want access to the SharePoint Online Security Token Service.

This is done by POSTing the following XML as the request body to:
https://login.microsoftonline.com/extSTS.srf

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
      xmlns:a="http://www.w3.org/2005/08/addressing"
      xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
    <a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
    <o:Security s:mustUnderstand="1"
       xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <o:UsernameToken>
        <o:Username>[username]</o:Username>
        <o:Password>[password]</o:Password>
      </o:UsernameToken>
    </o:Security>
  </s:Header>
  <s:Body>
    <t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
      <wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
        <a:EndpointReference>
          <a:Address>[endpoint]</a:Address>
        </a:EndpointReference>
      </wsp:AppliesTo>
      <t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
      <t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
      <t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
    </t:RequestSecurityToken>
  </s:Body>
</s:Envelope>

Requesting the security token
Requesting the security token

The response from the request includes the security token needed to get the access token. It is the value which has been stricken out in orange in the image below.

Response including the security token
Response including the security token

Get the access token

Once the security token has been retrieved it must be used to fetch the access token. The can be done by POSTing to the following URL with the security token as the request body:
https://yourdomain.sharepoint.com/_forms/default.aspx?wa=wsignin1.0

Request to fetch the access token, passing the security token
Request to fetch the access token, passing the security token

The response from this request includes couple of cookies which must be passed as headers with all future requests. They are marked with the ‘Set-Cookie’ header. We need the ones beginning with rtFa= and FedAuth=. They can be seen the below image of the response headers.

Response includes the access token cookies
Response includes the access token cookies

Get the request digest

The request digest is a .NET security feature that ensures any update requests are coming from a single session. It must also be included with any POST requests.

The request digest is fetched by POSTing to: https://yourdomain.sharepoint.com/_api/contextinfo
The access token cookies must be included as Cookie headers with the request as shown in the image below.

Request to fetch the request digest, passing access tokens
Request to fetch the request digest, passing access tokens

The response from the request will include the request digest in the XML response as in the image below. The entire contents of the FormDigestValue tag will required, including the date time portion and timezone offset (-0000).

Response containing the request digest value
Response containing the request digest value

Upload a document with metadata

Upload the document

Now that we have all the authentication headers we can make update calls into SharePoint Online as the user whose credentials we originally supplied when fetching the security token.

In order to upload a document perform the following POST request:
https://yourdomain.sharepoint.com/subweb/_api/web/lists/getbytitle(‘list name’)
/rootfolder/files/add(url='filename.csv',overwrite=true)

A number of headers must be send with the request including the access token cookies, the request digest (X-RequestDigest) and the accept header as shown in the image below. The body of the request must contain the content of the document being uploaded.

Request to upload a document to SharePoint Online
Request to upload a document to SharePoint Online

The response of this request contains some minimal metadata about the file and can be safely ignored. However, for completeness here it is.

Response JSON from the document upload request
Response JSON from the document upload request

The unique ID property could be used to fetch the document in order to perform metadata updates rather than URL as done in the following example.

Update document metadata

The final step which needs to take place is update the document in SharePoint with the relevant metadata.

This can be done with yet another POST request. This time to the following URI:
https://yourdomain.sharepoint.com/subweb/_api/web/lists/getbytitle(‘listTitle')
/rootfolder/files/getbyurl(url='serverRelFileUrl')/listitemallfields/validateupdatelistitem

All the headers sent with the previous request must be sent with this request as well. The request body is a JSON object which defines the metadata fields to be updated. The fieldname and fieldValue properties must be updated as required. Note that the fieldname property must be equal to the field internal name not the field display name. An example of this is in the image below.

Request to set metadata on a document in SharePoint
Request to set metadata on a document in SharePoint

The response from this request provides success notification for each individual field update as shown below.

Response from the document metadata update request
Response from the document metadata update request

So, this should now be enough to write an application in any server-side language which supports web requests and work against SharePoint Online. I’d love to see some implementations of this, please comment if you’ve done it.

I’d like to acknowledge the following posts as they were invaluable references:

 

SPC14 Sessions Reviewed (by someone else!)

I have been spending a little time deciding which sessions from the SharePoint Conference 2014 (SPC14) I want to watch over the coming weeks. Unfortunately I was unable to attend this year, but I know from previous years that it truly is an amazing conference with so much great content (and it’s in Vegas baby!).

Anyway, during my searching I came across a blog post (by CJG) which reviews (albeit briefly) every single session. Whaaaa..? I can’t confirm how valid his appraisals are as I’m still yet to get stuck in, however I was so impressed that I had to post a reference to the blog. You can find it here.

SharePoint Conference 2014
SharePoint Conference 2014

Excel data connection with Access 2013 App

I have had a hard time creating data connections with an Access 2013 App database. After a good few hours spent scouring the internet for a solution, and a good few more hours uncovering a “solution” that is underwhelming at best, I am happy to share with you my findings. I really hope that someone will leave a comment with a better solution at some point in the future.

This blog post will provide step-by-step guide on how to achieve a data connection from an Excel workbook (which can be hosted in SharePoint) to the SQL database behind an Access 2013 App. Once this is achieved, a good BI developer should have no trouble visualising the data captured via the Access App with the help of pivot tables, slicing and graphing.

The first step is to identify the server address and database to connect to along with the credentials required to authenticate.

  1. This can be done by navigating to the Access App, clicking the ‘settings’ icon, then clicking ‘Customize in Access’

    Launching the app database in Access
    Launching the app database in Access
  2. Download the .accdw file and open it to launch access
  3. Click ‘FILE’ in the ribbon
  4. Click ‘Manage’
  5. In the drop-down menu ensure that ‘From Any Location’ and ‘Enable Read-Only Connection’ are highlighted with pink squares. If not, click them

    Determining the Access database location and credentials
    Determining the Access database location and credentials
  6. Click ‘View Read-Only Connection Information’
  7. Take note of Server, Database, UserName, and Password from this dialog as you will need them all later

    Access connection information dialog
    Access connection information dialog

Next we use this information to create the data connection.

  1. Launch Excel
  2. Create a new external data connection ‘From Data Connection Wizard’

    Launching the Excel data connection wizard
    Launching the Excel data connection wizard
  3. Click ‘Other/Advanced’, then ‘Next’
    5otherAdvancedConnection
  4. Click ‘SQL Server Native Client 11.0’, then ‘Next’
    6SQLClient11
  5. On the ‘Data Link Properties’ dialog, uncheck the ‘Blank Password’ box and check the ‘Allow saving password’ box, then input the server name, user name, password, and database

    Configuring the data connection. Ensure you provide the database
    Configuring the data connection. Ensure you provide the database
  6. Test the connection, you should see a dialog box with ‘Test Connection Succeeded’
  7. Note that it is when you attempt to make a data connection without providing the database that you get the following error which I bet lead to to this post:

    Failure to connect to the Access 2013 App's SQL database
    Cannot open server ‘xxxxxxxxxx’ requested by the login. Client with IP address ‘00.000.000.000’ is not allowed to access the server. To enable access, use the Windows Azure Management Portal or run sp_set_firewall_rule on the master database to create a firewall rule for this IP address or address range. It may take up to five minutes for this change to take effect.
  8. You can now click ‘Ok’
  9. Uncheck the ‘Use Trusted Connection’ checkbox and replace the existing password with the correct one. Click ‘Ok’
  10. Select a table and click ‘Next’. You can get fancy here later, let’s just get it working first.
  11. Click ‘Finish’
  12. Click ‘Ok’
  13. The data connection will fail with the following error:

    Connection error
    Initialization of the data source failed. Check the database server or contact your administrator. Make sure the external database is available, and then try the operation again. If you see this message again, create a new data source to connect to the database.

The final frustration!

On the next dialog, uncheck the ‘Use Trusted Connection’ checkbox and replace the existing password with the correct one. Click ‘Ok’.
The second time it works. This process of providing the connection credentials twice is required not only upon the creation of the connection but also every time the data needs to be refreshed. It makes for a rather poor UX and it is a pretty awful scenario to have to explain to a client.

I really want to believe that there is a setting (most probably under the ‘All’ tab on the ‘Data Link Properties’ dialog) that will workaround this issue however I am yet to find it. Please leave a comment if you find a solution to this issue.