tag:blogger.com,1999:blog-41052136121449339422024-03-19T02:32:47.407-05:00Field Notes of a Computer GeekMy Pain, Your GainByron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.comBlogger456125tag:blogger.com,1999:blog-4105213612144933942.post-54508001257243615832023-08-06T14:46:00.002-05:002023-08-06T14:46:58.355-05:00Duplicate OneDrive Icon in File Explorer<p> I just got a new laptop and after doing most of the configuration I noticed that there were two OneDrive folders in File Explorer. The top one is named for my work tenant and the lower just named OneDrive. Typically the icon named OneDrive is for the personal version of OneDrive but I didn't have that configured. Only a OneDrive for Business account was configured.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfa4mWIgfYkhU_tSMI28qNy46oifEGkYxVsF-nvQWedNJBWUKuD6jJjuXYF-Ec90WNTRBAHyU6Ca54o4CfO4Iq4tsYPXtygLbH4hMQVPkXwmQ6ZsJY8AK-hhRfsJsrMArwz9vChuvY_Ejgqdck2_w0WH963jBmhv1TvGOjXsOMIwUUBuulBjuma6ZHCC28/s381/Screenshot%202023-08-06%20142733.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="312" data-original-width="381" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfa4mWIgfYkhU_tSMI28qNy46oifEGkYxVsF-nvQWedNJBWUKuD6jJjuXYF-Ec90WNTRBAHyU6Ca54o4CfO4Iq4tsYPXtygLbH4hMQVPkXwmQ6ZsJY8AK-hhRfsJsrMArwz9vChuvY_Ejgqdck2_w0WH963jBmhv1TvGOjXsOMIwUUBuulBjuma6ZHCC28/s16000/Screenshot%202023-08-06%20142733.png" /></a></div><p>When I clicked on the OneDrive icon it opened the same content as my OneDrive for Business. So, it appeared to redirect.</p><p>In my user profile folder (C:\Users\ByronWright\), I saw that there was a folder named OneDrive. When I deleted that folder, the redirection stopped and I got an error indicating that the folder didn't exist. Whoops. No fix there.</p><p>After a bit of searching around I found that I could remove it by removing the reference to it in the registry. That area of File Explorer is controlled by HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace. In that registry key, I had two registry keys that looked like GUIDs. Inside I could see one with the name OneDrive and the other was the OneDrive for Business name.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiK-QFVR9i4gvYn2Amk3184M7oOlYX26ovRNZFkVjSAi8jKxXMjoRv2PgrN-6qidnoCJzdl00M8iHvN008K2Ahsnc4gyRXunaSUQ7hGNG0MZSqqwu-zh8xrD_euhc_LR8V6J8FU9H2VA3VNGQCjfQs1MWZ0qmJedrDg_6OPXVCmjCHl15svFyWZwGW6V641/s847/Screenshot%202023-08-06%20142626.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="307" data-original-width="847" height="232" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiK-QFVR9i4gvYn2Amk3184M7oOlYX26ovRNZFkVjSAi8jKxXMjoRv2PgrN-6qidnoCJzdl00M8iHvN008K2Ahsnc4gyRXunaSUQ7hGNG0MZSqqwu-zh8xrD_euhc_LR8V6J8FU9H2VA3VNGQCjfQs1MWZ0qmJedrDg_6OPXVCmjCHl15svFyWZwGW6V641/w640-h232/Screenshot%202023-08-06%20142626.png" width="640" /></a></div><p>I deleted the registry key that referred to OneDrive (the entire key named like the GUID) and it immediately removed the extra reference to OneDrive from File Explorer.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwfO_v3bsA69ARTTlZBp6E04w6wtep1TvxSPNrYG03tPql3cG3igIvhbVMg5Ifzi4uWIodLk2H_dLEL4LDA0SBh23IDEdoC9HD7IoC9xM8qZZzomMObG3CAJadK_JHGjWRfiI8JYQfE7PjdDxc-AhYtjechfgcFR7u-kc7bZwT-QADnCf0RzQIozm7uJiV/s376/Screenshot%202023-08-06%20142840.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="308" data-original-width="376" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwfO_v3bsA69ARTTlZBp6E04w6wtep1TvxSPNrYG03tPql3cG3igIvhbVMg5Ifzi4uWIodLk2H_dLEL4LDA0SBh23IDEdoC9HD7IoC9xM8qZZzomMObG3CAJadK_JHGjWRfiI8JYQfE7PjdDxc-AhYtjechfgcFR7u-kc7bZwT-QADnCf0RzQIozm7uJiV/s16000/Screenshot%202023-08-06%20142840.png" /></a></div><p>My new laptop was originally purchased with Windows 11 Home edition which required me to sign in with a Microsoft account during setup. This configured personal OneDrive at the time. I upgraded the laptop to Windows 11 Pro and did a system reset but I think the reference to personal OneDrive was left over in the default user profile which was then copied when I signed in as my Azure AD (Entra ID) user.</p><p>For some additional information about this see:<a href=" https://superuser.com/questions/1144868/duplicated-onedrive-icon-in-explorer"> https://superuser.com/questions/1144868/duplicated-onedrive-icon-in-explorer</a></p><p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-68147685598499527112023-08-02T11:50:00.001-05:002023-08-02T11:50:32.530-05:00Immutable ID, ms-ds-consistencyGUID, and object GUID conversions<p>It seems like I'm constantly need to convert immutable IDs and GUIDs as part of my M365 migrations. To simplify this I finally got around to writing some functions that simplify the work instead of looking them up all the time.</p><p>I created the following:</p><ul style="text-align: left;"><li>Convert-ImmutableIDToGUID</li><li>Convert-ImmutableIDtoHexString</li><li>Convert-ImmutableIDtoByteArray</li><li>Convert-ByteArrayToGUID</li><li>Convert-GUIDToImmutableID</li><li>Convert-HexStringToGUID</li><li>Convert-HexStringToImmutableID</li><li>Convert-ByteArrayToImmutableID</li></ul><p>To make these available at a powershell prompt, you can load them as part of your powershell profile or dot source a script that contains.</p><p>Example of dot sourcing:</p><p>. c:\scripts\convertfunctions.ps1</p><p>Code for the functions:</p><!--HTML generated using hilite.me--><div style="background: rgb(17, 17, 17); border-color: gray; border-image: none; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="background-color: #0f140f; color: #008800; font-style: italic;"># Example immutable ID to play with</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;"># $ImmutableID = "GJo33fsMIUKvmIIyTOSjzg=="</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-ImmutableIDToGUID</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$ImmutableID</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$guid</span><span style="color: white;">=</span><span style="color: #0086d2;">[Guid]</span><span style="color: white;">(</span><span style="color: #0086d2;">[Convert]</span><span style="color: white;">::FromBase64String(</span><span style="color: #fb660a;">$ImmutableID</span><span style="color: white;">))</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$guid</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-ImmutableIDtoHexString</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$ImmutableID</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$hexstring</span><span style="color: white;">=(</span><span style="color: #0086d2;">[Convert]</span><span style="color: white;">::FromBase64String(</span><span style="color: #fb660a;">$ImmutableID</span><span style="color: white;">)</span> <span style="color: white;">|</span> <span style="color: #fb660a; font-weight: bold;">ForEach</span><span style="color: white;">-Object</span> <span style="color: white;">ToString</span> <span style="color: white;">X2)</span> <span style="color: white;">-join</span> <span style="color: #0086d2;">' '</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$hexstring</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-ImmutableIDtoByteArray</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$ImmutableID</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$bytearray</span><span style="color: white;">=</span><span style="color: #0086d2;">[Convert]</span><span style="color: white;">::FromBase64String(</span><span style="color: #fb660a;">$ImmutableID</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$bytearray</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">#When you retrieve ms-ds-consistencyGUID from AD it is a byte array</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">#to avoid this conversion use [guid]$user.'ms-ds-consistencyGUID'</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-ByteArrayToGUID</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$bytearray</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$guid</span><span style="color: white;">=</span><span style="color: #0086d2;">[Guid]</span><span style="color: white;">(</span><span style="color: #0086d2;">[Convert]</span><span style="color: white;">::FromBase64String(</span><span style="color: #0086d2;">[system.convert]</span><span style="color: white;">::ToBase64String(</span><span style="color: #fb660a;">$bytearray</span><span style="color: white;">)))</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$guid</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">#works with GUID object or string in GUID format</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-GUIDToImmutableID</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$guid</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$immutableID</span> <span style="color: white;">=</span> <span style="color: #0086d2;">[system.convert]</span><span style="color: white;">::ToBase64String((</span><span style="color: #0086d2;">[GUID]</span><span style="color: #fb660a;">$guid</span><span style="color: white;">).ToByteArray())</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$immutableID</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-HexStringToGUID</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$hexstring</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$guid</span> <span style="color: white;">=</span> <span style="color: #0086d2;">[GUID]</span><span style="color: white;">(</span><span style="color: #0086d2;">[byte[]]</span> <span style="color: white;">(-split</span> <span style="color: white;">((</span><span style="color: #fb660a;">$hexstring</span> <span style="color: white;">-replace</span> <span style="color: #0086d2;">" "</span><span style="color: white;">,</span> <span style="color: #0086d2;">""</span><span style="color: white;">)</span> <span style="color: white;">-replace</span> <span style="color: #0086d2;">'..'</span><span style="color: white;">,</span> <span style="color: #0086d2;">'0x$& '</span><span style="color: white;">)))</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$guid</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-HexStringToImmutableID</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$hexstring</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$ImmutableID</span> <span style="color: white;">=</span> <span style="color: #0086d2;">[system.convert]</span><span style="color: white;">::ToBase64String(</span><span style="color: #0086d2;">[byte[]]</span> <span style="color: white;">(-split</span> <span style="color: white;">((</span><span style="color: #fb660a;">$hexstring</span> <span style="color: white;">-replace</span> <span style="color: #0086d2;">" "</span><span style="color: white;">,</span> <span style="color: #0086d2;">""</span><span style="color: white;">)</span> <span style="color: white;">-replace</span> <span style="color: #0086d2;">'..'</span><span style="color: white;">,</span> <span style="color: #0086d2;">'0x$& '</span><span style="color: white;">)))</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$ImmutableID</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">function</span> <span style="color: white;">Convert-ByteArrayToImmutableID</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">param</span> <span style="color: white;">(</span><span style="color: #fb660a;">$bytearray</span><span style="color: white;">)</span>
<span style="color: #fb660a;">$ImmutableID</span> <span style="color: white;">=</span> <span style="color: #0086d2;">[system.convert]</span><span style="color: white;">::ToBase64String(</span><span style="color: #fb660a;">$bytearray</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #fb660a;">$ImmutableID</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;"><# Use Example</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">$msdsconsistencyGUID = (Get-ADUser Byron -properties *).ms-ds-consistencyGUID</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">$ImmutableID = Convert-ByteArrayToImmutableID -bytearray $msdsconsistencyGUID</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">Set-AzureADUser byron@domain.com -ImmutableID $ImmutableID</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">#></span>
</pre></div>
Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-19285918410066619852023-04-06T14:25:00.000-05:002023-04-06T14:25:12.577-05:00Using a specific version of Exchange ECP<p>When you have multiple versions of Exchange Server installed during a migration, you might need to specify which version of ECP you want to use. For example, migrating for Exchange 2013 to Exchange 2019 you might want to use one or the other. By default, when you sign in, you are directed to the version where your mailbox resides.</p><p>For users without mailboxes, they are redirected to the version of Exchange where the arbitration mailboxes are located. So, it's generally best to move the arbitration mailboxes to the newest version of Exchange server early in your migration process. <br /></p><p>When you access ECP, you can specify which version of ECP you want to use as part of the URL by adding <b>?ExchClientVerXX</b>. For example, <b>https://mail.contoso.com/ecp?ExchClientVer=15</b>.</p><p>You can use the following version numbers:</p><ul style="text-align: left;"><li>14 - Exchange 2010</li><li>15 - Exchange 2013</li><li>15.1 - Exchange 2016</li><li>15.2 - Exchange 2019</li></ul><p> References:</p><ul style="text-align: left;"><li><a href="https://learn.microsoft.com/en-us/answers/questions/578660/exchange-2019-ecp-redirects-to-exchange-2013">https://learn.microsoft.com/en-us/answers/questions/578660/exchange-2019-ecp-redirects-to-exchange-2013</a></li><li><a href="https://learn.microsoft.com/en-us/exchange/troubleshoot/client-connectivity/exchange-server-2013-2016-redirect-to-2010-owa-ecp">https://learn.microsoft.com/en-us/exchange/troubleshoot/client-connectivity/exchange-server-2013-2016-redirect-to-2010-owa-ecp</a><br /></li></ul>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-33749820459615629232023-03-14T14:35:00.001-05:002023-03-15T10:37:34.040-05:00DMARC, DKIM, and SPF for email<p>There is a huge amount of spam and phishing emails on the internet. To combat this recipients are getting more and more aggressive in trying to block it. As a mail system administrator, you want the valid email from your domain to be accepted by recipients. <br /></p><p>There are three different (but related) technologies that you should be aware of that validate email from your domain:</p><ul style="text-align: left;"><li>SPF (Sender Policy Framework)<br /></li><li>DKIM (DomainKeys Identified Mail)<br /></li><li>DMARC (Domain Message Authentication Reporting and Conformance)<br /></li></ul><p>All of these technologies are used by recipients to validate that email is sent from an authorized email system. Correctly configuring these technologies increase the likelihood that email you send won't be incorrectly categorized as spam.</p><p>These technologies are also useful to prevent spoofing email from entering your email system. Many phishing attempts try to appear as though mail comes from your domain. SPF and DKIM help identify these. For example, a message from it@contoso.com would be identified as unauthorized when coming from a random IP address on the internet rather than your own authorized servers. <br /></p><h2 style="text-align: left;">SPF</h2><p>An SPF record is a TXT record in DNS that list authorized sources for email in your domain. For example, the SPF record in contoso.com defines which servers are authorized to send email from the contoso.com domain. You need to identify all of the services that send email on behalf of your domain before you configure the SPF record. Valid sources can be applications or marketing services in addition to your email provider.<br /></p><p>The value in the TXT record contains host names (a) and IP addresses (ip4 or ip6) that are allowed to send email for your domain. If you have three servers in your organization that send email to the internet, then you need to include the IP addresses or host names for those servers in your SPF record. Remember that the IP address and host name are from the perspective of hosts on the internet. You need to include host names that can be resolved by internet DNS or IP addresses that are routable on the internet.</p><p>If you're using a hosting service, such as Microsoft 365 or Gmail, to send messages, they'll provide instructions on how to configure SPF for their service. In many cases, they maintain lists of host names and IP addresses so you don't have to. In your SPF record, you refer to their configuration by using the include option.</p><p>Example:</p><ul style="text-align: left;"><li>Type: TXT</li><li>Name: @ <br /></li><li>Value: v=spf1 include:spf.protection.outlook.com ip4:1.1.1.1 -all</li></ul><p>Description of record contents: <br /></p><ul style="text-align: left;"><li>All SPF records start v=spf1 to indicate that they are an SPF record.</li><li>The include option in this example is used for Microsoft 365. Microsoft maintains the detailed information required at this DNS location.</li><li>The ip4 option specifies that the server 1.1.1.1 is allowed to send email on behalf of the domain.</li><li>The all option is always includes at the end of the SPF record. When it's configured with a dash (-), then mail not from a valid host should be a hard fail. When it's configured with a tilde (~), then mail not from a valid host should be a soft fail.</li></ul><p> Additional resources:</p><ul style="text-align: left;"><li>How to Create and Add an SPF Record<br /><a href="https://dmarcian.com/create-spf-record/">https://dmarcian.com/create-spf-record/</a></li><li>SPF Record Syntax<br /><a href="https://dmarcian.com/spf-syntax-table/">https://dmarcian.com/spf-syntax-table/</a></li><li>SPF Record Check<br /><a href="https://mxtoolbox.com/spf.aspx">https://mxtoolbox.com/spf.aspx</a></li></ul><p></p><h2 style="text-align: left;">DKIM</h2><p>DKIM adds a digital signature to email messages to verify that they are from an authorized source. The recpient email system reads the digital signature and verifies that it's valid for your domain. The digital signature is validated based on DNS records that you publish.</p><p>The process for implementing DKIM varies depending on the email provider. Microsoft has an easy to use system in Exchange Online where they manage most of the DNS work for you. Other vendors provide information for you to create your own DKIM DNS records. And, some vendors and applications don't support DKIM at all. For example, on-premises Microsoft Exchange Server doesn't have DKIM functionality included.</p><p>In Microsoft 365 (Exchange Online) the DKIM process is pretty simple to implement. You need to create two CNAME records in your domain that point to records created by Microsoft and then turn on DKIM for the domain. The following example record is for contoso.com:</p><ul style="text-align: left;"><li>Type: CNAME</li><li>Name: selector1._domainkey</li><li>Value: selector1-contoso-com._domainkey.contoso.onmicrosoft.com</li></ul><p>When an email that's signed using DKIM is received, it includes a digital signature. The digital signature refers to the selector that contains the key used to used validate the signature. The DNS example above is for selector1 in the contoso.com domain. The CNAME record directs recipients to selector1-contoso-com._domainkey.contoso.onmicrosoft.com to obtain the necessary key for validation. <br /></p><p>Additional resources:</p><ul style="text-align: left;"><li>What is DKIM?<br /><a href="https://dmarcian.com/what-is-dkim/">https://dmarcian.com/what-is-dkim/</a></li><li>Help prevent spoofing and spam with DKIM (Google)<br /><a href="https://support.google.com/a/answer/174124?hl=en">https://support.google.com/a/answer/174124?hl=en</a></li><li>Use DKIM to validate outbound email sent from your custom domain (Exchange Online)<br /><a href="https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/email-authentication-dkim-configure?view=o365-worldwide">https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/email-authentication-dkim-configure?view=o365-worldwide</a></li></ul><p></p><h2 style="text-align: left;">DMARC</h2><p>A DMARC record is a TXT record in DNS that tells recipients two main things about email from your domain:</p><ul style="text-align: left;"><li>How to handle email that fails validity checks.</li><li>Where to send reports about email that fails validity checks.</li></ul><p>Email is validated when it passes either the SPF check or the DKIM check. This means that you can begin using DMARC when you have implemented SPF but don't have DKIM. This is important since some email systems don't support DKIM.<br /></p><p>The p option in the record allows to you define what action should be taken on messages that fail SPF and DKIM. The potential values are:</p><ul style="text-align: left;"><li>none - Specifies that no action should be taken and messages that don't pass should be delivered. You can use this during initial setup when you're still validating your configuration.</li><li>quarantine - Specifies that unvalidated messages should be send to quarantine or junk email. You can use this when you think your setup is configured correctly but still want more time to validate.</li><li>reject - Specifies that unvalidated messages should be discarded. You should use this option when you configuration is fully validated and you know that your SPF and DKIM are configured properly.</li></ul><p>An important benefit of DMARC is the ability to define an email address for reporting messages that were not validated. A recipient email system that receives in invalid email sends a report to the email address. Then you can review the reports and identify any valid systems or applications sending email from your domain and fix the configuration. This email address is defined by the rua option.</p><p>For a busy email system, it's not realistic to review all of the DMARC reports manually. Instead, there are many services such as dmarcian (<a href="http://dmarcian.com">dmarcian.com</a>) that will host a mailbox for you and process the DMARC reports into usable information.</p><p>Example DMARC record:</p><ul style="text-align: left;"><li>Type: TXT</li><li>Name: _dmarc</li><li>Value: v=DMARC1; p=reject; rua=mailto:dmarcreports@contoso.com;</li></ul><p> Description of record contents:</p><ul style="text-align: left;"><li>The DMARC record for a domain always has the name _dmarc.</li><li>A DMARC record always starts with v=DMARC1.</li><li>The p option defines the action for unvalidated messages.</li><li>The rua option defines the email address for reports.</li><li>The semicolon (;) at the end is optional.</li></ul><p>Additional resources:</p><ul style="text-align: left;"><li>DMARC Overview<br /><a href="https://dmarc.org/overview/">https://dmarc.org/overview/</a></li><li>DMARC Record Wizard<br /><a href="https://dmarcian.com/dmarc-record-wizard/">https://dmarcian.com/dmarc-record-wizard/</a></li></ul><h2 style="text-align: left;">Summary</h2><p>Every organization should have SPF and DMARC in place. You can do this without having DKIM enabled because the SPF passing is sufficient. However, DKIM is an additional assurance of validity for email delivery and you should implement it where you can.</p><p>Be aware that receiving email systems can do whatever they want with inbound mail for spam filtering. So, it's possible that even if you have SPF, DKIM, and DMARC configured correctly, a message can be blocked, but it's much less likely.<br /></p><p> </p><p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-60884495956328379672023-02-21T15:47:00.000-06:002023-02-21T15:47:39.913-06:00Cannot ping the selected CA when renewing certificate<p>I've configured a test environment for migrating certification authorities (CAs) from Windows Server 2008 R2 to Windows Server 2019. The process is well documented and I didn't expect any issues. However, after migrating the root CA to Windows Server 2019, I couldn't renew the CA certificate on the issuing CA still running on Windows Server 2008 R2.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgjrZ_RInGGDn3WbK1JAK_63dnykuCMn77NlaU0CgppiMUyUjU2CXiEhFDdW5ZUpiCCprZdtDlp8iSS4NiRSIdHq-Q94_Foo0ieaJLSz0bAVFvQ1hzuDckg9GZaCrKXtnmFPjDDShG3MKq-R9AhNJjhTJDLFhHOCkovluGARTlmn1eX7HEc3h1J_H6uQ/s726/RemoteDesktopManager_2n3quWaack.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="252" data-original-width="726" height="139" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgjrZ_RInGGDn3WbK1JAK_63dnykuCMn77NlaU0CgppiMUyUjU2CXiEhFDdW5ZUpiCCprZdtDlp8iSS4NiRSIdHq-Q94_Foo0ieaJLSz0bAVFvQ1hzuDckg9GZaCrKXtnmFPjDDShG3MKq-R9AhNJjhTJDLFhHOCkovluGARTlmn1eX7HEc3h1J_H6uQ/w400-h139/RemoteDesktopManager_2n3quWaack.png" width="400" /></a></div><br />Attempting to renew the certificate I got this error:<p></p><blockquote><p>Cannot ping the selected CA. Please make sure the CA is running. The RPC server is unavailable. 0x800706ba (WIN32: 1722)</p></blockquote><p>On receiving this error, I tried the common things like verifying network connectivity and disabling Windows Firewall just in case. Some searching around indicated this is sometimes a result of COM+ permissions, but it looked OK. Eventually I found this event on the Windows Server 2019 CA which led me to the source of the error.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjolfH27RpbjUPmRHcZc6VH1CTSzrpz3jP91O0PHA-5VW6NqpsIVheefcjIuOT6sXLmXN1dUxSav1hbg5NOdEfszx8Q7CK0ANyXCjq31Gc9gWqDrtjir-YxwYZISyxbQysI4oVvmM1wzCMMDBb4roFWqXSM6xL08p3YBXAyUyzRiL8iDG9_Pcd_4N0pyg/s1094/RemoteDesktopManager_oRJnsIx0xL.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="767" data-original-width="1094" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjolfH27RpbjUPmRHcZc6VH1CTSzrpz3jP91O0PHA-5VW6NqpsIVheefcjIuOT6sXLmXN1dUxSav1hbg5NOdEfszx8Q7CK0ANyXCjq31Gc9gWqDrtjir-YxwYZISyxbQysI4oVvmM1wzCMMDBb4roFWqXSM6xL08p3YBXAyUyzRiL8iDG9_Pcd_4N0pyg/w400-h280/RemoteDesktopManager_oRJnsIx0xL.png" width="400" /></a></div><br />Event information:<p></p><ul style="text-align: left;"><li>Log Name: System</li><li>Source: DistributedCOM</li><li>Event ID: 10036</li><li>Description: The server-side authentication level policy does not allow the user CERTTEST\Administrator SID (S-1-5-21-2892548479-535578393-614425194-500) from address 192.168.0.112 to activate DCOM server. Please raise the activation authentication level at least to RPC_C_AUTHN_LEVEL_PKT_INTEGRITY in client application.<br /></li></ul><p>The actual cause is DCOM security hardening introduced by Microsoft in KB5004442. This update requires a higher level of security that Windows Server 2008 R2 doesn't support. Until March 2023 you can disable the security hardening with a registry key, but after that you can't downgrade the security.</p><ul style="text-align: left;"><li>Path : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Ole\AppCompat</li><li>Value Name: "RequireIntegrityActivationAuthenticationLevel"</li><li>Type: dword</li><li>Value Data: default = 0x00000000 means disabled. 0x00000001 means enabled. If this value is not defined, it will default to enabled.<br /></li></ul><div><p>For applications that are not updated to request the higher level of authentication security, Microsoft has added new DCOM client functionality to automatically raise the security level in the November 2022 update. This should mitigate issues for most client applications on Windows Server 2012 R2 and newer or Windows 8.1 and newer.<br /></p><p></p><p></p><p>For detailed information about KB5004442 see:</p><ul style="text-align: left;"><li><a href="https://support.microsoft.com/en-us/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c">https://support.microsoft.com/en-us/topic/kb5004442-manage-changes-for-windows-dcom-server-security-feature-bypass-cve-2021-26414-f1400b52-c141-43d2-941e-37ed901c769c</a><br /></li></ul><p><br /></p></div>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-40360650502303523082023-01-15T13:50:00.006-06:002023-01-29T13:27:40.177-06:00Updating and Verifying LDAPS Certificate<p>Windows domain controllers (DCs) can be used by applications as an LDAP server for user authentication or application data storage. To enable encrypted communication with LDAP on a DC, you need to install a certificate on the DC. This uses TLS similar to how a web server does for HTTPS. </p><p>If you have an enterprise CA in your Active Directory (AD) forest, a certificate is automatically issued to your DCs for encrypting LDAP communication. If you don't have an enterprise CA in your AD forest, then a certificate isn't issued to DCs automatically and LDAP communication is unencrypted.</p><p>Any server certificate added the the computer store on the DC is automatically used and available for secure LDAP. You don't need to configure the DC to use the certificate. You also have the option to install a certificate in the NT Directory Services store. If a certificate is installed into the NT Directory Services store, it is preferred over certificates in the computer store. This is useful when there are multiple services running on the DC (such as IIS) and you want to ensure that secure LDAP is using the correct certificate.</p><p>To update the certificate used by secure LDAP, put the new/renewed certificate in the same certificate store as the certificate that's being replaced. The newer certificate will be automatically selected and used for secure LDAP within a few minutes.</p><p>If you are a bit paranoid and want to confirm that the new certificate is being used before you remove the older certificate, there are no built-in Windows tools or logging events that display the information. You can use LDP.exe to test connectivity to port 636 and verify that secure LDAP is working, but this doesn't give you information about which certificate is being used.</p><p>However, you can download and use OpenSSL to verify which certificate is being used. OpenSSL can be installed on a client computer for testing connectivity rather than the DC. The following command connects to LDAPS on port 636 and displays information about the certificate being used to secure communication:</p>
<!--HTML generated using hilite.me-->
<div style="background: rgb(248, 248, 248); border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">openssl s_client -connect DCName.domain.com:636
</pre></div>
<p>You can use the output from this command to verify the name in the certificate and expiration date.<br /></p><p><span style="font-family: 'Courier Std';">Reference:</span></p><ul style="text-align: left;"><li><a href="https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority"><span style="font-family: 'Courier Std';">https://learn.microsoft.com/en-us/troubleshoot/windows-server/identity/enable-ldap-over-ssl-3rd-certification-authority</span></a></li><li><span style="font-family: 'Courier Std';"><a href="https://wiki.openssl.org/index.php/Binaries">https://wiki.openssl.org/index.php/Binaries</a> <br /></span></li><li><a href="https://www.openssl.org/docs/man1.0.2/man1/openssl-s_client.html"><span style="font-family: 'Courier Std';">https://www.openssl.org/docs/man1.0.2/man1/openssl-s_client.html</span></a><span style="font-family: 'Courier Std';"> </span><br /></li></ul><p><span style="font-family: 'Courier Std'; font-size: 11pt; line-height: 107%;"> </span></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-11245371176079943852022-08-29T16:27:00.000-05:002022-08-29T16:27:07.022-05:00Error adding authorized senders for distribution group<p>A client has a distribution group in Exchange Online with many members that restricts who can send to the group. This is a common scenario and works quite well.</p><p>Configuring specified senders is done in Delivery management. In the example below, Byron Wright and Jeff Smith are allowed to send. Normally to add another sender, you search for them, add, and then save.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOyg4oXzBofzmnYqbBC9rJcm1yrzESeeKNZRx86uFwDTj3IyQ9ZU8kfD1z7qhdJDuKgv8D-GZXd9y1ANy82AcfW5-WaYSZpEtIlzI-O_vI3G-jsw52mT4B91w5zt73g0hnegXscogezWp38qDZLzuiToPcz2kWaaaMAl-MwFEsvR6kQtFAvotZOPf3Cg/s1096/GrpDeliveryManagement.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Delivery management settings for a distribution group that shows specified senders option." border="0" data-original-height="768" data-original-width="1096" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOyg4oXzBofzmnYqbBC9rJcm1yrzESeeKNZRx86uFwDTj3IyQ9ZU8kfD1z7qhdJDuKgv8D-GZXd9y1ANy82AcfW5-WaYSZpEtIlzI-O_vI3G-jsw52mT4B91w5zt73g0hnegXscogezWp38qDZLzuiToPcz2kWaaaMAl-MwFEsvR6kQtFAvotZOPf3Cg/w400-h280/GrpDeliveryManagement.png" title="Delivery management" width="400" /></a></div><br /><p>When we were adding an additional user, we got the following error: <br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj3GNK5IqTQ-nZC1J7E29t5exeZyfEBvcE76-vpGDJunS-3msHCNnttuAPedaQv2kfsZh3ygqZqzYbbg0besYVQCDFZjWbuKqrdsFrwJGc2p5GjhdgImoZwFOjeIPnUFQJFwoL1sGWXMZC2p-jssanK5iRT9QfZxDAAOxiZohC3NlSi0_EAzAskRh40Q/s592/DEliverymanagementerrror.png" style="margin-left: 1em; margin-right: 1em;"><img alt="There are multiple recipients matching the identity "<username>". Please specify a unique value." border="0" data-original-height="140" data-original-width="592" height="95" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj3GNK5IqTQ-nZC1J7E29t5exeZyfEBvcE76-vpGDJunS-3msHCNnttuAPedaQv2kfsZh3ygqZqzYbbg0besYVQCDFZjWbuKqrdsFrwJGc2p5GjhdgImoZwFOjeIPnUFQJFwoL1sGWXMZC2p-jssanK5iRT9QfZxDAAOxiZohC3NlSi0_EAzAskRh40Q/w400-h95/DEliverymanagementerrror.png" title="There are multiple recipients matching the identity "<username>". Please specify a unique value." width="400" /></a></div><div style="text-align: center;"><span style="font-size: x-small;">Error executing request: There are multiple recipients matching<br /> the identity "<user>". Please specify a unique value.</span></div><p></p><p>This was verify confusing because we knew all of the email addresses were unique. However, when searching, I found a document mentioning the same error for creating rules in OWA. The cause of the error was multiple recipients having the same display name.</p><ul style="text-align: left;"><li><div id="there-are-multiple-recipients-matching-the-identity-error-when-creating-inbox-rules-in-owa-or-ems"><a href="https://docs.microsoft.com/en-us/exchange/troubleshoot/outlook-on-the-web-issues/there-are-multiple-recipients-matching-the-identity-error" target="_blank">There are multiple recipients matching the identity error when creating inbox rules in OWA or EMS</a></div></li></ul><p>Sure enough, we went through the list of specified senders and found several that had duplicates with the same display names (student and staff accounts for example). After we changed the duplicate display names and synced them up to Azure AD we could add and remove specified senders as expected.</p><p>The recipient indicated in the error message is not necessarily the identity with the duplicate Display Name. If you attempt to remove users one by one and save the changes, the successful save indicates which is a duplicate. If you're using the method, make sure to note the allowed senders before you start editing.<br /></p><p>Otherwise, you can also use the Admin center search bar to see if there are duplicates for a name. This lets you search across recipient types for users, contacts, groups, and teams.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPVwqeclQIEKjKwdw2ZrToZxu3wOvx6lbJ5KD6oeYIDh5N_kgtxNkEZ8yDFHZ7b2W5au4O8yh_Focs-_ii2dSKfgLM6EdvwbxpwpsExmTr_-aKb6nZvPd3Pb6wLHoh9MhnHvpnCEXf9N1yyyXnG-V5aS1rgVSDGACh0NYrgG_HHWawWpgRqXFBHgyCvA/s1379/AdminCenterSearch.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="81" data-original-width="1379" height="24" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPVwqeclQIEKjKwdw2ZrToZxu3wOvx6lbJ5KD6oeYIDh5N_kgtxNkEZ8yDFHZ7b2W5au4O8yh_Focs-_ii2dSKfgLM6EdvwbxpwpsExmTr_-aKb6nZvPd3Pb6wLHoh9MhnHvpnCEXf9N1yyyXnG-V5aS1rgVSDGACh0NYrgG_HHWawWpgRqXFBHgyCvA/w400-h24/AdminCenterSearch.png" width="400" /></a></div><p></p><p>There seems to be some inconsistency in how the uniqueness of users is evaluated. It seems that when you initially add a user it's based on the email address and the display name issue only pops up when you go to edit the list afterwards. For example, we could add Bob@adatum.com (Bob Smith) as a sender even though there was another Bob Smith. But after doing that we'd get the error if we attempted to edit the list.<br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-86189053744599827872022-07-12T13:48:00.000-05:002022-07-12T13:48:01.235-05:00Is my domain name attached to a tenant?<p>A common issue is verifying whether a domain name is already attached to a Microsoft 365/Azure AD tenant. If you don't check ahead of time, you often find that you can't add a new domain name to a tenant at a critical time during a project.<br /></p><p>To check whether a domain name is already in Microsoft 365:</p><ul style="text-align: left;"><li><a href="https://www.whatismytenantid.com/">https://www.whatismytenantid.com/</a></li></ul><p>If there is a tenant ID, then it's already registered.</p><p>This often gets us to the point where no one knows for sure what tenant and who might be able to log in. It might have been created for volume licensing or a Power BI trial. There's really no way to know.</p><p>Instructions on how to get the domain name back:</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/domains-admin-takeover">https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/domains-admin-takeover</a></li></ul><p> </p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-91010218802639901682022-05-02T08:50:00.000-05:002022-05-02T08:51:02.443-05:00Mailbox moves between Exchange Online tenants<p>For the last several years I've been involved mostly in tenant to tenant migration projects. These projects typically include migration of email, teams, and SharePoint. My focus is on email migration.</p><p>For migration, there are a number of third party tools such as MigrationWiz (Bit Titan) and On Demand Migration for Email (Quest ODME) that can be used. I don't know all of the details for all tools, but at least in the case of ODME, the migration is copies mailbox data, but doesn't synchronize it. So, if you migrate data from source to target and the user later deleted data from the source, it is not deleted in the target. Occasionally, this is annoying and causes confusion for users.</p><p>Microsoft has tenant-to-tenant mailbox moves in Preview. This type of move is a migration batch like moving mailboxes between on-premises Exchange and Exchange Online. In this case, you get true syncing which is a better quality move. It looks like a bit of a pain to setup and manage, but I expect the experience will improve over time.</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/microsoft-365/enterprise/cross-tenant-mailbox-migration">https://docs.microsoft.com/en-us/microsoft-365/enterprise/cross-tenant-mailbox-migration</a></li></ul><p>The documentation claims that mailbox permissions like Full Access are maintained as long as users are part of the same migration batch. It does not mention inbox rules.</p><p>Another interesting item that's coming up is Microsoft 365 cross-tenant SMTP domain sharing. This allows an email domain to exist in two Microsoft 365 tenants simultaneously and will make it easier to manage incremental migrations between tenants when you want to maintain the same email address. The feature is currently in private preview, but is scheduled to be moved into public preview in June 2022.</p><p>You can monitor the development status at:</p><ul style="text-align: left;"><li><a href="https://www.microsoft.com/en-ca/microsoft-365/roadmap?filters=&searchterms=67161">https://www.microsoft.com/en-ca/microsoft-365/roadmap?filters=&searchterms=67161</a><br /></li></ul>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-74869497031495425392022-02-12T14:41:00.002-06:002022-05-02T08:51:13.483-05:00Query recently created users in Azure AD<p>Recently had a project where we wanted to identify users created an automated process in the last day. This script gets the job done.</p>
<!--HTML generated using hilite.me--><div style="background: rgb(255, 255, 255) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto; color: black;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #aaaaaa; font-style: italic;">#Gather a list of recently created users</span>
<span style="color: #aaaaaa; font-style: italic;">#specify time that will be compared against -24 is the most recent 24 hours</span>
<span style="color: #aaaaaa; font-style: italic;">#you can use the option funtion addDays for a longer time period</span>
<span style="color: #aaaaaa; font-style: italic;">#Note that time from AzureAD is UTC</span>
<span style="color: #aa0000;">$time</span> = (<span style="color: #00aaaa;">get-date</span>).ToUniversalTime().AddHours(-24)
<span style="color: #aa0000;">$users</span> = <span style="color: #00aaaa;">Get-AzureADUser</span> -All
<span style="color: #aa0000;">$newusers</span> = <span style="color: #00aaaa;">New-Object</span> Collections.Generic.List
<span style="color: #0000aa;">foreach</span> (<span style="color: #aa0000;">$u</span> <span style="color: #0000aa;">in</span> <span style="color: #aa0000;">$users</span>) {
<span style="color: #aaaaaa; font-style: italic;"># Write-Host $u.ExtensionProperty.createdDateTime</span>
<span style="color: #0000aa;">If</span> (<span style="color: #aa0000;">[datetime]$u</span>.ExtensionProperty.createdDateTime -gt <span style="color: #aa0000;">$time</span>) {
<span style="color: #aa0000;">$newusers</span>.Add(<span style="color: #aa0000;">$u</span>)
}
}
<span style="color: #00aaaa;">Write-host</span> <span style="color: #aa5500;">"There are "</span> <span style="color: #aa0000;">$newusers</span>.count <span style="color: #aa5500;">" new users"</span>
<span style="color: #aa0000;">$newusers</span>
</pre></div>
<p><br /></p><p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com2tag:blogger.com,1999:blog-4105213612144933942.post-79829024747616493092022-02-04T07:57:00.001-06:002022-02-27T21:29:01.139-06:00Dell XPS 13 9380 Pulsating Fan<p><b>Short version:</b> BIOS 1.17.0 released Jan 2022 appears to fix pulsating fan issue for Dell XPS 13 9380 in Windows 11</p><p><b>Update:</b> I'm still seeing the pulsating fan occasionally, but less than before the BIOS update.<br /></p><p>I recently upgraded my Dell XPS 13 9380 to Windows 11. The entire process was smooth and fast. I was pleasantly surprised by how well it went.</p><p>About an hour after the upgrade, as the computer was going to sleep, the fan started to turn on and off at about 1 second intervals. This pulsating stopped if you woke up the computer or (oddly) if you unplugged it from power.</p><p>When in doubt, update all drivers and firmware. So, I used Dell Support Assist to download and update all the drivers and firmware. There were about 10 updates. And after applying updates and a restart all seemed good for about 24 hours.</p><p>The next afternoon, the fan started to pulsate again. Some Windows Updates had applied since the Dell updates were installed and I was concerned that might be causing the issue. In desperation, I ran Support Assist again, and it showed two updates to apply. One of which was BIOS firmware version 1.17.0.</p><p>The BIOS update showed in the history as having been installed yesterday, but it must not have applied properly. This time, the BIOS update applied properly and the problem appears to be resolved.<br /></p><p> <br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-68010618379595407412021-11-22T12:34:00.001-06:002021-11-22T12:34:49.924-06:00User Profile Ramifications when Renaming Users on Azure AD-Joined Computers<p>I'm starting to work more with devices that are Azure AD-joined rather than domain-joined. One of my key questions was what happens to user profiles when an Azure AD user sign-in name (UPN) is changed. I was pleasantly surprised by how well it worked.</p><p>For my testing, I created an Azure AD user and signed in to create a profile. During sign-in, I created a PIN for authentication. While signed in, I also configured an Outlook profile and OneDrive. Then I tried changing the domain portion of the username and the userid portion of the username. The results were the same:</p><ul style="text-align: left;"><li>I could still sign in with the PIN.</li><li>I could sign in as the same user (username displayed on sign-in screen) with the password. <br /></li><li>I could sign in with the new username (typed in) and password.</li></ul><p>After signing in:</p><ul style="text-align: left;"><li>The workplace account was updated to the new username.</li><li>Outlook was still able to sign-in without user intervention and updated the account.<br /></li><li>OneDrive continued to function without user intervention and updated the account.</li><li>The same Windows 10 user profile was retained. <br /></li></ul><p> </p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-3406620636845010632021-11-17T15:20:00.004-06:002021-11-18T15:47:01.100-06:00Managing Microsoft 365 Licenses by using Microsoft Graph<p>Microsoft has announced that after June 2022, the MSOL and AzureAD cmdlets for managing user licenses in Microsoft 365 will cease working. These cmdlets rely on management functionality that is being retired. To manage licenses programmatically, you need to start using Microsoft Graph.</p><p>Here is the announcement:</p><ul style="text-align: left;"><li><a href="https://techcommunity.microsoft.com/t5/azure-active-directory-identity/migrate-your-apps-to-access-the-license-managements-apis-from/ba-p/2464366">https://techcommunity.microsoft.com/t5/azure-active-directory-identity/migrate-your-apps-to-access-the-license-managements-apis-from/ba-p/2464366</a><br /></li></ul><p>Microsoft Graph is a web-API that you can use to manage Microsoft 365 users, groups, and services. If you're a programmer, then perhaps the idea of building a web request to perform administrative tasks sounds like a good idea. However, for an admin guy like me that typically uses PowerShell cmdlets for management tasks, building web requests is a bit painful. Fortunately, the Microsoft Graph PowerShell SDK has been released that provides PowerShell cmdlets to access Microsoft Graph features.</p><p>To get more information about the Microsoft Graph PowerShell SDK:</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/graph/powershell/installation">https://docs.microsoft.com/en-us/graph/powershell/installation</a></li></ul><h2 style="text-align: left;"> Connecting with Microsoft Graph<br /></h2><p>Just like you use <span style="font-family: courier;">Connect-AzureAD</span> or <span style="font-family: courier;">Connect-MsolService</span>, for Microsoft Graph, you use <span style="font-family: courier;">Connect-MgGraph</span>. When you connect, you need to specify a scope that defines your permissions. So, unlike previous versions, the connection does not automatically gain the full permissions based on your roles like Global Admin. I haven't experimented with exactly which scopes are required to manage user licenses. However, I can confirm that the following example does work.</p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007020;">Connect-MgGraph</span> -Scopes <span style="color: #4070a0;">"User.ReadWrite.All"</span>,<span style="color: #4070a0;">"Directory.ReadWrite.All"</span>
</pre></div>
<p>To get more information about Microsoft Graph scopes:</p><ul style="text-align: left;"><li> <a href="https://docs.microsoft.com/en-us/graph/permissions-reference">https://docs.microsoft.com/en-us/graph/permissions-reference</a><br /></li></ul><h2 style="text-align: left;">License Structure and Naming</h2><p style="text-align: left;">If you've been managing licenses through the web interface in Microsoft 365 or the MSOL cmdlets, you're used to seeing license names such as Office 365 E3. When you manage licenses by using Microsoft Graph, you need to know the <span style="font-family: courier;">SkuId</span> property of the licenses available in your tenant. You can obtain the SkuId for a license by using <span style="font-family: courier;">Get-MgSubscribedSku</span> as shown in the following figure. The <span style="font-family: courier;">SkuPartNumber </span>property is a more user friendly name that you can recognize.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXag0jLu-7SCUWxY2u1fHqSj1J3tAxFcxiF_EdnLoBCSUPGFLZ4iOqTqk6m63-jGyONFRMWZMFDfjGl0h9H2WIEq3wy235vhwtYNlAF1JU_VQCiXcndKb5qA-j4xv94srkL6vCd04FwwFa/s1202/Get-MgSubscribedSku.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="823" data-original-width="1202" height="438" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXag0jLu-7SCUWxY2u1fHqSj1J3tAxFcxiF_EdnLoBCSUPGFLZ4iOqTqk6m63-jGyONFRMWZMFDfjGl0h9H2WIEq3wy235vhwtYNlAF1JU_VQCiXcndKb5qA-j4xv94srkL6vCd04FwwFa/w640-h438/Get-MgSubscribedSku.png" width="640" /></a></div><p>Within each licenses type, there are also service plans. These correlate with apps provided by a license such as Exchange Online (Plan 2). To enable or disable the service plans, you need to use the <span style="font-family: courier;">ServicePlanId</span> for a service plan. If you place your subscribed SKUs in a variable, you can view the service plans included in the SKU as shown in the following figure.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPK8-hUTnNuXpBSfMAEmOAY-r9KfpLErYiCfV3-32371iYVmIQiln7rASCmn9nyCpbRrVBCFKWSluIb8EaaJ4RIucV1zvHHYUQYwKT_78yyow-wvBFHVjTUU2qRz948oMBu2dMLTUm283x/s1144/ServicePlans.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="758" data-original-width="1144" height="424" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPK8-hUTnNuXpBSfMAEmOAY-r9KfpLErYiCfV3-32371iYVmIQiln7rASCmn9nyCpbRrVBCFKWSluIb8EaaJ4RIucV1zvHHYUQYwKT_78yyow-wvBFHVjTUU2qRz948oMBu2dMLTUm283x/w640-h424/ServicePlans.png" width="640" /></a></div><p></p><p>To get a list of generally available SKUs and their service plans:</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference">https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference</a><br /></li></ul><h2 style="text-align: left;">Viewing Assigned Licenses</h2><p>You can view the licenses assigned to a user by using the <span style="font-family: courier;">Get-MgUserLicenseDetail</span> cmdlet as shown in the following example:</p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007020;">Get-MgUserLicenseDetail</span> -UserId user@domain.com
</pre></div>
<p>The results of this command return the users license assigned to the user. An array of licenses is returned if multiple licenses have been assigned. Within each license returned, you can view the ServicePlans property to see if any service plans have been disabled for a user.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDF7l8FGW7KhIt7BBXVEb_D1PAxTjSf6XaBsIa1Ug7RAQbZc8d0es20u_O1nL6osuMuKUJrwan7yym5n5rdqQgLxT4FtGn3bAXW14ODnoYJdUHgxHkcMOxWE16b00OMq9koi2eoH3ap7Q7/s1188/LicenseDetail.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="946" data-original-width="1188" height="510" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDF7l8FGW7KhIt7BBXVEb_D1PAxTjSf6XaBsIa1Ug7RAQbZc8d0es20u_O1nL6osuMuKUJrwan7yym5n5rdqQgLxT4FtGn3bAXW14ODnoYJdUHgxHkcMOxWE16b00OMq9koi2eoH3ap7Q7/w640-h510/LicenseDetail.png" width="640" /></a></div><p>You can also query assigned license information by using <span style="font-family: courier;">Get-MgUser</span>. The licensing information isn't returned by default and you need to specify that the <span style="font-family: courier;">AssignedLicenses</span> property will be retrieved as shown in the figure below. Notice that these results list the service plans that are disabled for a license.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyffudrmB5sWL_3fTL-FZD7yVYnix2I0cx0IkMGoSAK48QNiGi6kolW-yjdhgdC7-91yCE_XoDMUt-gardW17Q8RK5egpEivtmLbY37XJ9GxzjK8_HGkseQZo17En5mO0JVq2hYKof53nr/s1361/mguser-license.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="194" data-original-width="1361" height="92" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyffudrmB5sWL_3fTL-FZD7yVYnix2I0cx0IkMGoSAK48QNiGi6kolW-yjdhgdC7-91yCE_XoDMUt-gardW17Q8RK5egpEivtmLbY37XJ9GxzjK8_HGkseQZo17En5mO0JVq2hYKof53nr/w640-h92/mguser-license.png" width="640" /></a></div><p></p><h2 style="text-align: left;">Querying Users with Assigned Licenses</h2><p style="text-align: left;">If you want to query all of the users with a specific license, you can do this by using <span style="font-family: courier;">Get-MgUser</span> with a filter for a specific <span style="font-family: courier;">SkuId</span>. The example below shows the syntax.</p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007020;">Get-MgUser</span> -Filter <span style="color: #4070a0;">"assignedLicenses/any(x:x/skuId eq 78e66a63-337a-4a9a-8959-41c6654dfb56)"</span> -Property AssignedLicenses,UserPrincipalName,Id
</pre></div>
<p style="text-align: left;">When you are filtering based on <span style="font-family: courier;">AssignedLicenses </span>there are some limitations on the results returned. By default, only 100 results are returned. If you use the <span style="font-family: courier;">PageSize </span>parameter, you can specify up to 999 results are returned as shown below.<br /></p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007020;">Get-MgUser</span> -Filter <span style="color: #4070a0;">"assignedLicenses/any(x:x/skuId eq 78e66a63-337a-4a9a-8959-41c6654dfb56)"</span> -PageSize 999
</pre></div>
<p>If you have a larger tenant, and you try to use the <span style="font-family: courier;">All </span>parameter to return results larger than these limits, you will be the following error <span style="font-family: courier;"><span style="color: red;">Get-MgUser : The specified page token value has expired and can no longer be included in your request.</span></span>To avoid this error, you need to query a list of all users and then filter by using <span style="font-family: courier;">Where-Object</span> as shown below.</p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #bb60d5;">$allusers</span> = <span style="color: #007020;">Get-MgUser</span> -All -Property AssignedLicenses,UserPrincipalName,Id,DisplayName
<span style="color: #bb60d5;">$A1plusUsers</span> = <span style="color: #bb60d5;">$allusers</span> | <span style="color: #007020;">Where-Object</span> {<span style="color: #bb60d5;">$_</span>.AssignedLicenses.SkuId <span style="color: #666666;">-contains</span> <span style="color: #4070a0;">"78e66a63-337a-4a9a-8959-41c6654dfb56"</span>}
</pre></div>
<p></p>
<h2 style="text-align: left;">Modifying Assigned Licenses<br /></h2><p>To add or remove licenses for a user, you use the <span style="font-family: courier;">Set-MgUserLicense</span> cmdlet. When you run the cmdlet, you need to provide the following parameters:</p><ul style="text-align: left;"><li><span style="font-family: courier;">UserId</span>. The user being modified. You can specify the user by the object <span style="font-family: courier;">Id </span>or <span style="font-family: courier;">UserPrincipalName</span>.<br /></li><li><span style="font-family: courier;">AddLicenses</span>. A hash table that specifies the <span style="font-family: courier;">SkuId </span>of a license being added and the <span style="font-family: courier;">ServicePlanId </span>of any service plans that are being disabled.</li><li><span style="font-family: courier;">RemoveLicenses</span>. A string that identifies the <span style="font-family: courier;">SkuId </span>of a license being removed.<br /></li></ul><p>The following code shows how to build a hash table for the <span style="font-family: courier;">AddLicenses </span>parameter. This specifies a license <span style="font-family: courier;">SkuId </span>and a service plan that's disabled in the license.<br /></p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #bb60d5;">$A1FacultySku</span> = @{
SkuID = <span style="color: #4070a0;">"94763226-9b3c-4e75-a931-5c89701abe66"</span>
DisabledPlans = <span style="color: #4070a0;">"9aaf7827-d63c-4b61-89c3-182f06f82e5c"</span>
}
</pre></div>
<p>The following code lists a <span style="font-family: courier;">SkuID </span>that will be disabled.</p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #bb60d5;">$A1PlusFacultySku</span> = <span style="color: #4070a0;">"78e66a63-337a-4a9a-8959-41c6654dfb56"</span>
</pre></div>
<p>The command that modifies the user license is below. Note that the <span style="font-family: courier;">UserId </span>parameter will accept a UPN also.<br /></p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007020;">Set-MgUserLicense</span> -UserId <span style="color: #bb60d5;">$User</span>.Id -AddLicenses <span style="color: #bb60d5;">$A1FacultySku</span> -RemoveLicenses <span style="color: #bb60d5;">$A1PlusFacultySku</span>
</pre></div>
<p>The <span style="font-family: courier;">RemoveLicenses </span>and <span style="font-family: courier;">AddLicenses </span>parameters are mandatory. If you don't provide an empty array, you'll get an error such as <span style="font-family: courier;"><span style="color: red;">Set-MgUserLicense : One or more parameters of the function import 'assignLicense' are missing from the request payload. The missing parameters are: removeLicenses. </span></span>If you don't want to remove any licenses, you need to provide an empty array for <span style="font-family: courier;">RemoveLicenses </span>as shown below. If you are only removing licenses, you need to provide an empty array for the <span style="font-family: courier;">AddLicenses </span>parameter.<br /></p>
<!--HTML generated using hilite.me--><div style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #007020;">Set-MgUserLicense</span> -UserId <span style="color: #bb60d5;">$User</span>.id -AddLicenses <span style="color: #bb60d5;">$A1FacultySku</span> -RemoveLicenses <span style="color: #bb60d5;">@()</span>
</pre></div>
<p></p><p>If you want to modify the disabled plans for a licenses, you build a new hash table with the license and all of the plans you want disabled. Then you apply the new hash table with the <span style="font-family: courier;">AddLicenses </span>parameter. The new license assignment overwrites the existing license assignment.</p><p>If you want to add multiple licenses, you can provide a comma separated list of hash tables. I have not explicitly tested, but I think providing an array with the hash tables would also work.</p><p>If you want to remove multiple licenses, create an array with the <span style="font-family: courier;">SkuID</span>s that you want to remove.<br /></p><br /><p style="text-align: left;"><br /></p><p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-32259168730063324262021-10-28T14:30:00.007-05:002021-10-28T16:35:16.400-05:00AADSTS90072 User Does not Exist in Tenant<p>During a recent migration project from one tenant to another, a test user was unable to sign in. The sign-in page in Office 365 redirected to AD FS on-premises for authentication. The user credentials worked in AD FS and the web browser was redirected back to Office 365. Then this error was displayed: <br /></p>
<pre>Sign in
Sorry, but we’re having trouble signing you in.
AADSTS90072: User account 'Bob.Smith@domain.com' from identity
provider'urn:sso.domain.com:domain.com' does not exist in
tenant 'Byron Co' and cannot access the application '4765445b-
32c6-49b0-83e6-1d93765276ca'(OfficeHome) in that tenant. The
account needs to be added as an external user in the tenant
first. Sign out and sign in again with a different Azure
Active Directory user account.</pre>
<p>This error indicates that the user authenticated by AD FS does not exist in Azure AD. Based on the UPN, Bob.Smith@domain.com existed both in on-premises AD and Azure AD. So, there is some other property being used to match the two objects after AD FS authentication.</p><p>To understand where this broke down, you need to understand how objects in AD are linked with objects in Azure AD. There is an ImmutableID property on Azure AD users that links Azure AD users to on-premises AD users. Early implementations of Azure AD Connect (or Dirsync) copied the object GUID from on-premises AD and used that value for the ImmutableID. This worked well until you migrated user objects to new AD domain or AD forest where they'd have a different GUID.</p><p>New implementations of Azure AD Connect use ms-ds-ConsistencyGUID in the on-premises user object instead of GUID. This value can be copied between domains to preserve synchronization during object migrations. By default ms-ds-ConsistencyGUID is populated with the same value as the object GUID the first time the object is synced.</p><p>AD FS authentication uses the object GUID or ms-ds-ConsistencyGUID during authentication. This value must match the ImmutableID in AzureAD to allow authentication to complete. The older instructions for configuring AD FS authentication manually had you configure a rule for object GUID. If you use Azure AD Connect to configure AD FS then it creates rules that use ms-ds-ConsistencyGUID if populated or object GUID. This article talks about the rule configuration: <a href="https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-management#modclaims">https://docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-fed-management#modclaims</a>.</p><p>If you update your deployment of Azure AD Connect to use ms-ds-ConsistencyGUID as the source anchor and forget to update AD FS to allow ms-ds-ConsistencyGUID in the authentication process, AD FS authentication will continue to work because object GUID and ms-ds-ConsistencyGIUD are the same value by default. However when you start migrating objects and retain the ms-ds-ConsistencyGUID (which will now be different from the object GUID) authentication starts to fail because the token passed back to Office 365 for authentication contains the object GUID which doesn't match the immutable ID/ms-ds-ConsistencyGUID. Thus the error message above.</p><p>In our case, the AD migration tool we were using copied the ms-ds-ConsistencyGUID from a source AD domain to target AD domain which caused our authentication issue. Because users were getting new mailboxes in this migration, we didn't need to maintain ms-ds-ConsistencyGUID. Our short term fix was to copy the object GUID value and place that in ms-ds-ConsistencyGUID and immutableID. However, the correct long term solution is to update AD FS to correctly use ms-ds-ConsistencyGUID during authentication.</p><p>This article has some examples you can use to convert object GUID to ms-ds-ConsistencyGUID and ImmutableID: <a href="http://byronwright.blogspot.com/2020/10/convert-immutableid-to-hex.html">http://byronwright.blogspot.com/2020/10/convert-immutableid-to-hex.html</a>.<br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p>
Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-20495845138035258902021-10-20T16:03:00.003-05:002021-10-20T16:03:32.886-05:00Query the Signed in User When Running Script as System<p>I'm working on a desktop migration project where we run some PowerShell scripts to prepare the computer for migration. As part of this we need the locally signed in user.</p><p>Normally, you can obtain locally signed in user from an environment variable: <br /></p>
<!--HTML generated using hilite.me--><div style="background: rgb(255, 255, 255) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #996633;">$env:USERNAME</span>
</pre></div>
<p>However, we're running the script as SYSTEM. So, that returned value is incorrect. That value is the username associated with the PowerShell instance.</p><p>You can query the signed in user when you run a script as SYSTEM by using Get-WmiObject:</p>
<p></p>
<!--HTML generated using hilite.me--><div style="background: rgb(255, 255, 255) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; color: black; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;">(<span style="color: #007020;">Get-WmiObject</span> -ClassName Win32_ComputerSystem).Username
</pre></div>
<p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-21446734634846180262021-09-05T14:53:00.002-05:002021-10-20T16:04:55.334-05:00Set User Department Based on OU<p>When you create dynamic distribution groups on-premises, you have the option to create them based on organizational unit. In Exchange Online (EXO), you don't have this option because Azure AD doesn't have the OUs for your tenant.</p><p>There are many attributes available for creating dynamic distribution groups in EXO and one available through the web interface is department. So, to simulate OU-based groups, we can set the department attribute.</p><p>To do this, I created a script on a DC that runs once per hour and sets the Department attribute based on the OU. When you run a script as SYSTEM on a DC, it has the ability to modify Active Directory. The function below is the core of the script.<br /></p>
<!--HTML generated using hilite.me--><div style="color: #000000; background: rgb(248, 248, 248) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">#Function requires the OU as a distinguished name</span>
<span style="color: green; font-weight: bold;">Function</span> <span style="color: green;">Set-UserDepartment</span> {
<span style="color: green; font-weight: bold;">param</span>(
[<span style="color: green; font-weight: bold;">parameter</span>(<span style="color: green; font-weight: bold;">Mandatory</span>=<span style="color: #19177c;">$true</span>)] <span style="color: #19177c;">$OU</span>,
[<span style="color: green; font-weight: bold;">parameter</span>(<span style="color: green; font-weight: bold;">Mandatory</span>=<span style="color: #19177c;">$true</span>)] <span style="color: #19177c;">$Department</span>
)
<span style="color: green;">Write-Host</span> <span style="color: #ba2121;">""</span>
<span style="color: green;">Write-Host</span> <span style="color: #ba2121;">"Setting department attribute as $Department for users in $OU"</span>
<span style="color: #408080; font-style: italic;">#Find null values</span>
<span style="color: #19177c;">$nullusers</span> = <span style="color: green;">Get-ADUser</span> -Filter {Department <span style="color: #666666;">-notlike</span> <span style="color: #ba2121;">"*"</span>} -Properties Department -SearchBase <span style="color: #19177c;">$OU</span>
<span style="color: #408080; font-style: italic;">#Find wrong value</span>
<span style="color: #19177c;">$wrongvalue</span> = <span style="color: green;">Get-ADUser</span> -Filter {Department <span style="color: #666666;">-ne</span> <span style="color: #19177c;">$Department</span>} -Properties Department -SearchBase <span style="color: #19177c;">$OU</span>
<span style="color: #408080; font-style: italic;">#Create one array of all users to fix</span>
<span style="color: #19177c;">$users</span> = <span style="color: #19177c;">$nullusers</span> + <span style="color: #19177c;">$wrongvalue</span>
<span style="color: green;">Write-Host</span> <span style="color: #ba2121;">"null value: "</span> <span style="color: #19177c;">$nullusers</span>.count
<span style="color: green;">Write-Host</span> <span style="color: #ba2121;">"wrong value: "</span> <span style="color: #19177c;">$wrongvalue</span>.count
<span style="color: #408080; font-style: italic;">#Set department</span>
<span style="color: green; font-weight: bold;">Foreach</span> (<span style="color: #19177c;">$u</span> <span style="color: green; font-weight: bold;">in</span> <span style="color: #19177c;">$users</span>) {
<span style="color: green;">Set-ADUser</span> <span style="color: #19177c;">$u</span>.DistinguishedName -Department <span style="color: #19177c;">$Department</span> <span style="color: #408080; font-style: italic;"># -WhatIf</span>
}
}
</pre></div><p></p><p>This function:</p><ul style="text-align: left;"><li>Expects the OU to be passed as a distinguished name</li><li>Finds users in the OU (and sub-OUs) with the department set to $null</li><li>Finds users in the OU (and sub-OUs) with the incorrect department</li><li>Sets the Department value as specified when you call the function for all users identified<br /></li></ul><p>Querying the users that don't have department set correctly and calling Set-ADUser for only those users is much faster than setting all users each time.<br /></p><p>The count for null value or wrong value is incorrect when there is a single item because a single item is not an array. You can improve this by forcing them to be an array before populating them. Or, checking whether it's single item first, but for my purposes, this was sufficient.</p><p>Within the script, you can call the function as many times as required to set the attributes. You just pass the OU and the department value to the function like below.</p><p></p>
<!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">#Call function to set department for Marketing</span>
<span style="color: green;">Set-UserDepartment</span> -OU <span style="color: #ba2121;">"OU=Marketing,DC=Contoso,dc=com"</span> -Department Marketing
</pre></div>
<p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-41173378584923155992021-08-10T13:22:00.002-05:002021-08-10T13:23:32.471-05:00Script to Update DNS Record Permissions<div><p> When you have secure dynamic update configured for DNS zones, the individual DNS records are protected by security permissions. The host records are typically secured by the associated computer account. The PTR records are typically secured by the DHCP server account or the associated computer account depending on whether it's a static or dynamic IP address.</p><p>If you have highly available DHCP servers, they should be configured with a user account for dynamic DNS updates. This user account is used by both DHCP servers to ensure that records created by one DHCP server can be updated by the other.</p><p>If you have highly available DHCP and don't use a shared account, then you'll see errors in the DHCP event log (Event ID 20322) indicating that the DNS record couldn't be updated. After you configure the shared account, the permissions on the DNS records will still be incorrect. The following script adds Full Control permissions on A and PTR records for the shared account. This allows the properly configured DHCP servers to update existing records.</p><p>You'll need to do some editing on this script for your environment:</p><ul style="text-align: left;"><li>$DynamicDnsUser needs to be set to your shared user account.</li><li>$Server needs to be set to the name of your DNS server.</li><li>$zones needs to contain the zones you want to modify. </li></ul></div><div style="text-align: left;"><br /></div><div>
<!--HTML generated using hilite.me--><div style="background: rgb(248, 248, 248) none repeat scroll 0% 0%; border-color: gray; border-image: none 100% / 1 / 0 stretch; border-style: solid; border-width: 0.1em 0.1em 0.1em 0.8em; border: medium solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;"><pre style="line-height: 125%; margin: 0px;"><span style="color: #408080; font-style: italic;">#DynamicDnsUser is the user configured on both DHCP servers for dynamic DNS</span>
<span style="color: #408080; font-style: italic;">#This uses the SamAccountName of the user</span>
<span style="color: #19177c;">$DynamicDnsUser</span> = <span style="color: green;">Get-ADUser</span> ddnsuser
<span style="color: #408080; font-style: italic;">#Query the SID for this user and create a ACE allowing full control</span>
<span style="color: #19177c;">$SID</span> = <span style="color: green;">New-Object</span> System.Security.Principal.SecurityIdentifier <span style="color: #19177c;">$DynamicDnsUser</span>.SID.Value
<span style="color: #19177c;">$ACE</span> = <span style="color: green;">New-Object</span> System.DirectoryServices.ActiveDirectoryAccessRule <span style="color: #19177c;">$SID</span>, <span style="color: #ba2121;">"GenericAll"</span>, <span style="color: #ba2121;">"Allow"</span>
<span style="color: #408080; font-style: italic;">#When running DNS cmdlets from a workstation</span>
<span style="color: #408080; font-style: italic;">#you need to specify the server you are acting on</span>
<span style="color: #19177c;">$server</span> = <span style="color: #ba2121;">"DNSServer"</span>
<span style="color: #408080; font-style: italic;">#query a list of all zones to loop through and set permissions</span>
<span style="color: #408080; font-style: italic;">#$zones = Get-DnsServerZone -ComputerName $server</span>
<span style="color: #408080; font-style: italic;">#$zones = $zones | where ZoneType -eq Primary</span>
<span style="color: #19177c;">$zones</span> = <span style="color: green;">Get-DnsServerZone</span> <span style="color: #ba2121;">"40.10.in-addr.arpa"</span>
<span style="color: green; font-weight: bold;">Foreach</span> (<span style="color: #19177c;">$zone</span> <span style="color: green; font-weight: bold;">in</span> <span style="color: #19177c;">$zones</span>) {
<span style="color: #408080; font-style: italic;">#Query all records in the zone</span>
<span style="color: #408080; font-style: italic;">#Record type ensures that the get the correct type of records</span>
<span style="color: #408080; font-style: italic;">#based on forward or reverse lookup zones</span>
<span style="color: green; font-weight: bold;">If</span> (<span style="color: #19177c;">$zone</span>.IsReverseLookupZone <span style="color: #666666;">-eq</span> <span style="color: #19177c;">$true</span>) {
<span style="color: #19177c;">$recordType</span> = <span style="color: #ba2121;">"PTR"</span>
} <span style="color: green; font-weight: bold;">Else</span> {
<span style="color: #19177c;">$recordType</span> = <span style="color: #ba2121;">"A"</span>
}
<span style="color: #19177c;">$records</span> = <span style="color: green;">Get-DnsServerResourceRecord</span> -ComputerName <span style="color: #19177c;">$server</span> -ZoneName <span style="color: #19177c;">$zone</span>.ZoneName -RRType <span style="color: #19177c;">$recordType</span>
<span style="color: #408080; font-style: italic;">#Loop through all records in the zone and add</span>
<span style="color: #408080; font-style: italic;">#the ACE for the dynamic DNS user</span>
<span style="color: #408080; font-style: italic;">#Need to set the location to AD: for the *-ACL cmdlets to work</span>
<span style="color: green; font-weight: bold;">Foreach</span> (<span style="color: #19177c;">$record</span> <span style="color: green; font-weight: bold;">in</span> <span style="color: #19177c;">$records</span>) {
<span style="color: green;">Push-Location</span> -Path AD:
<span style="color: #19177c;">$ACL</span> = <span style="color: green;">Get-Acl</span> -Path <span style="color: #19177c;">$record</span>.DistinguishedName
<span style="color: #19177c;">$ACL</span>.AddAccessRule(<span style="color: #19177c;">$ACE</span>)
<span style="color: #19177c;">$ACL</span> | <span style="color: green;">Set-Acl</span> -Path <span style="color: #19177c;">$record</span>.DistinguishedName
<span style="color: green;">Pop-Location</span>
} <span style="color: #408080; font-style: italic;">#end foreach records</span>
} <span style="color: #408080; font-style: italic;">#end foreach zones</span>
</pre></div><p></p><p>Please be aware this only works to allow the DHCP server to update the DNS records. In most organizations, the host records are dynamically updated by the individual computers. If you want to add the correct computer account to a DNS record, then you need to approach this differently.</p><p>The following link has a script that finds a computer account that matches a host record and assigns permissions to that computer account. This script was used as a starting point for my script above.<br /></p></div><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/answers/questions/355589/script-to-change-permissions-on-dns-records.html">https://docs.microsoft.com/en-us/answers/questions/355589/script-to-change-permissions-on-dns-records.html</a><br /></li></ul>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-13797215851616245422021-07-21T13:49:00.004-05:002021-07-22T08:05:26.690-05:00Dynamic DNS Settings for Highly Available DHCP Servers<p>Windows DHCP servers can integrate with DNS to perform dynamic DNS on behalf of clients. This is useful when DHCP clients such as printers or mobile phones are not able to perform their own dynamic DNS updates. The DHCP server can also perform secure dynamic DNS updates when the client can't.</p><p>You can configure dynamic DNS settings at the IPv4 node (server level) or at the individual scope. If you don't configure dynamic DNS settings at the scope level, they are inherited from the server level. If you update dynamic DNS settings at the server level those new settings are used by all scopes that don't have dynamic DNS settings explicitly defined.</p><p>Unfortunately, there is no easy way to identify when dynamic DNS settings are configured at the scope level instead of the server level. If the settings are different then they are definitely configured at the scope level. But, if the settings are the same, they could be configured at either level.<br /></p><p>When you have scopes configured for high availability with two Windows DHCP servers, then both servers can service the scope. If you have accidentally configure the dynamic DNS settings at the IPv4 node differently on the two servers, it can provide inconsistent settings for clients depending on which DHCP server provides the lease.</p><p>For example, DHCP1 and DHCP2 are configured with a failover relationship that is in load balancing mode. Scopes using this failover relationship service half of requests using DHCP1 and half of requests using DHCP1.</p><p>At the IPv4 node of DHCP1, it is configured to perform dynamic DNS updates on when requested by the clients.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm9HOs2uizaKb14GyVgVCc8DmpHzLYLAmLmAEfNScJRehyphenhyphenefS4Vlemo8BFGsbb5YfxGSSFKJXSKkb_48vsmcldGwnWbSo8QyGHBsCFRQlXNWrZSuQ3yR2EAmhs1nkxHpRLPMUaPwzQS38D/s682/DHCP1.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="682" data-original-width="599" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm9HOs2uizaKb14GyVgVCc8DmpHzLYLAmLmAEfNScJRehyphenhyphenefS4Vlemo8BFGsbb5YfxGSSFKJXSKkb_48vsmcldGwnWbSo8QyGHBsCFRQlXNWrZSuQ3yR2EAmhs1nkxHpRLPMUaPwzQS38D/w351-h400/DHCP1.png" width="351" /></a></div><br /><p>At the IPv4 node of DHCP2, it is configured to perform dynamic updates for all clients.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBVR0noBEyggd0BaZGEDsA0VG4BWST3vZQIY9wpmyzCjhbnxhsKP8hr9SIKM9DquEbMxR-zDASH2DDJfyDUW8JaNW5qY5SBVFRGqpV8IlZjN4MbeHKzVsJgrBOrHX_CGeS2HGJucioT9HI/s682/DHCP2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="682" data-original-width="599" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBVR0noBEyggd0BaZGEDsA0VG4BWST3vZQIY9wpmyzCjhbnxhsKP8hr9SIKM9DquEbMxR-zDASH2DDJfyDUW8JaNW5qY5SBVFRGqpV8IlZjN4MbeHKzVsJgrBOrHX_CGeS2HGJucioT9HI/w351-h400/DHCP2.png" width="351" /></a></div><br /><p>If you create a new scope, named Client LAN and configure it to use the failover relationship, the scope appears on both servers. When you view the DNS tab in the properties of Client LAN, the settings match the server settings. So, the settings you see vary depending on which DHCP server that the DHCP admin console is connected to.</p><p>When a client leases an address from DHCP1, the dynamic DNS settings from the IPv4 node of DHCP1 are used. When a client leases an address from DHCP2, the dynamic DNS settings from the IPv4 node of DHCP2 are used.</p><p>To avoid this, you can do the following:</p><ul style="text-align: left;"><li>Ensure that the IPv4 settings are the same on both servers (you really should)</li><li>Manually configure the dynamic DNS settings in each scope</li></ul><h2 style="text-align: left;">Secure Dynamic Update Credentails<br /></h2><div><p>Another consideration when using highly available DHCP with dynamic DNS updates is the credentials for secure updates in DNS. By default, when a DHCP server creates a DNS record that allows only secure dynamic updates, the record is secured with permissions based on the computer account of the DHCP server. When two DHCP servers are working together, this can result in DHCP1 creating a DNS record that DHCP2 can't update.</p><p>To ensure that both highly available DHCP servers can service all records created by either server, you need to configure a user account that is used by both servers to secure dynamic DNS records. This is configured on each server on the Advanced tab in the properties of IPv4.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKLMwRfxS26_mq8fcwQ748kitWlzPEcmD4YE-dkc84YhIE27bv2KyueXwny-871y3xqG2idfYwn3Zah31GGIxMxuqOAHUKSr2_XABj_LHs_6wLCKGDCCOWl81Tc2FOIZOEGaG4ic8ymDi9/s683/DHCPCredentials.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="683" data-original-width="601" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKLMwRfxS26_mq8fcwQ748kitWlzPEcmD4YE-dkc84YhIE27bv2KyueXwny-871y3xqG2idfYwn3Zah31GGIxMxuqOAHUKSr2_XABj_LHs_6wLCKGDCCOWl81Tc2FOIZOEGaG4ic8ymDi9/w353-h400/DHCPCredentials.png" width="353" /></a></div> </div><div>After configuring the DNS dynamic update credentials on both servers, the DNS records are secured by that user account. Since both servers use the same user account, they can update DNS records created by the other DHCP server. This user account does not require any special permissions. It just needs to be a member of Domain Users. And of course, you should set the password to not expire.<br /></div><div> </div><div>If the DNS zones are configured to allow insecure dynamic updates then security is ignored during dynamic DNS updates and the credentials are not important.<br /><p><br /></p><p><br /></p></div>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-4707815440442786832021-06-01T13:59:00.000-05:002021-06-01T13:59:17.576-05:00DNS Forwarding Timeouts<p>When you configure forwarders on Windows DNS servers, it's not obvious what the timeout values are. You might intuitively think that more forwarders is better. In reality, with the default values, you're just kidding yourself.</p><p>DNS forwarders have a default timeout of 3 seconds. If the first forwarder does not respond within 3 seconds then the second forwarder is contacted, and so forth.</p><p>However, there is an overall recursion timeout of 8 seconds. After 8 seconds no more forwarders will be contacted. So, best case, the process looks like this:</p><ul style="text-align: left;"><li>0s - Contact forwarder 1</li><li>3s - Contact forwarder 2</li><li>6s - Contact forwarder 3</li><li>8s - recursion timeout (process ends)</li></ul><p>As you can see, only the first three forwarders listed are ever used. Putting more than 3 forwarders on a DNS server is misleading because forwarders 4 and up will never be contacted.</p><p>Conditional forwarders have a similar process but with different timeout values. Conditional forwarders have a default timeout of 5 seconds along with the recursion timeout of 8 seconds. This means that only two conditional forwarders are ever contacted.</p><p>The conditional forwarder process looks like this:</p><ul style="text-align: left;"><li>0s - Contact conditional forwarder 1</li><li>5s - Contact conditional forwarder 2</li><li>8s - recursion timeout (process ends)</li></ul><p>Again, I suggest don't ever list more than two conditional forwarders or it is misleading.</p><p>If you want to allow additional forwarders or conditional forwarders to be queried, you can modify the default values in the registry of the DNS servers. However, be sure to do this on all DNS servers so that it is consistent. And, document it as part of your domain controller build process so that it is configured on new domain controllers too. <br /></p><p>Registry keys to modify the default timeout values:</p><ul style="text-align: left;"><li>Recursion timeout (per DNS server)</li><ul><li><code>HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\**RecursionTimeout</code></li></ul><li>Forwarding timeout (per DNS server)</li><ul><li><code>HKLM\SYSTEM\CurrentControlSet\Services\DNS\Parameters\**ForwardingTimeout</code> <br /></li></ul><li>Forwarder timeout (per zone/conditional forwarder)</li><ul><li> <code>HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DNS Server\Zones\ <zone_name>\ForwarderTimeout</code></li></ul></ul><p>For more detailed information about this process, see:</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/forwarders-resolution-timeouts#what-is-the-default-behavior-of-a-dns-server-when-more-than-two-dns-servers-are-configured-as-conditional-forwarders" target="_blank">NET: DNS: Forwarders and conditional forwarders resolution timeouts</a></li></ul>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-59829920357828145282021-03-24T08:28:00.001-05:002021-03-24T08:28:30.800-05:00Script to compare file presence in directory structures<p>I'm still running into a few organizations that need to convert their sysvol replication from FRS to DFS-R. The process for doing that is quite straight forward, but if FRS replication is broken, then there is the risk that some data could be lost during this process.</p><p>As part of due diligence before converting, I like to compare the contents of sysvol among the domain controllers. The PDC emulator is the default source for new sysvol data. So, I compare that to other domain controllers. To help me with this I've created a PowerShell script that compares the files to identify any that are not the same on two servers.</p><p>I use this for sysvol, but you could use it to compare any two data structures. This script does not compare time stamps, file size, or file contents. It only looks for presence.</p><p> <br /></p>
<!-- HTML generated using hilite.me --><div style="background: #f0f0f0; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;color: #000000;"><pre style="margin: 0; line-height: 125%"><span style="color: #60a0b0; font-style: italic">#Domain controller path to sysvol</span>
<span style="color: #bb60d5">$srcpath</span>=<span style="color: #4070a0">"\\Server1\sysvol"</span>
<span style="color: #bb60d5">$targetpath</span>=<span style="color: #4070a0">"\\Server2\sysvol"</span>
<span style="color: #60a0b0; font-style: italic">#Get list of files</span>
<span style="color: #bb60d5">$srcfiles</span> = <span style="color: #007020">get-childitem</span> -Path <span style="color: #bb60d5">$srcpath</span> <span style="color: #666666">-File</span> -Recurse
<span style="color: #bb60d5">$targetfiles</span> = <span style="color: #007020">get-childitem</span> -Path <span style="color: #bb60d5">$targetpath</span> <span style="color: #666666">-File</span> -Recurse
<span style="color: #60a0b0; font-style: italic">#Add property looks only at relative file name path</span>
<span style="color: #60a0b0; font-style: italic">#Required for proper comparison without server name</span>
<span style="color: #007020; font-weight: bold">Foreach</span> (<span style="color: #bb60d5">$file</span> <span style="color: #007020; font-weight: bold">in</span> <span style="color: #bb60d5">$srcfiles</span>) {
<span style="color: #bb60d5">$cleanpath</span> = (<span style="color: #bb60d5">$file</span>.FullName).replace(<span style="color: #bb60d5">$srcpath</span>,<span style="color: #4070a0">""</span>)
<span style="color: #bb60d5">$file</span> | <span style="color: #007020">Add-Member</span> -NotePropertyName ShortPath -NotePropertyValue <span style="color: #bb60d5">$cleanpath</span> -Force
}
<span style="color: #007020; font-weight: bold">Foreach</span> (<span style="color: #bb60d5">$file</span> <span style="color: #007020; font-weight: bold">in</span> <span style="color: #bb60d5">$targetfiles</span>) {
<span style="color: #bb60d5">$cleanpath</span> = (<span style="color: #bb60d5">$file</span>.FullName).replace(<span style="color: #bb60d5">$targetpath</span>,<span style="color: #4070a0">""</span>)
<span style="color: #bb60d5">$file</span> | <span style="color: #007020">Add-Member</span> -NotePropertyName ShortPath -NotePropertyValue <span style="color: #bb60d5">$cleanpath</span> -Force
}
<span style="color: #bb60d5">$dif</span> = <span style="color: #007020">Compare-Object</span> <span style="color: #bb60d5">$srcfiles</span> <span style="color: #bb60d5">$targetfiles</span> -property ShortPath <span style="color: #60a0b0; font-style: italic">#-PassThru</span>
<span style="color: #007020">Write-Host</span> <span style="color: #4070a0">"Source ( <= ) is: "</span> <span style="color: #bb60d5">$srcpath</span>
<span style="color: #007020">Write-Host</span> <span style="color: #4070a0">"Target ( => ) is: "</span> <span style="color: #bb60d5">$targetpath</span>
<span style="color: #007020">Write-Host</span> <span style="color: #4070a0">""</span>
<span style="color: #bb60d5">$dif</span>
</pre></div>
Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-54929113293289616232021-03-23T19:23:00.002-05:002021-03-23T19:24:14.869-05:000x80070780: The file cannot be accessed by the system<p>Today got this error when trying to import a virtual machine on my newly rebuilt Hyper-V VM host running Windows Server 2019:</p><blockquote><p>0x80070780: The file cannot be accessed by the system</p></blockquote><p>I tried playing with permissions but it kept erroring out saying that it couldn't access the vhdx files. Finally, I realized that when I rebuilt the VM host, I forgot to install the data deduplication feature and thus the system could see the file names, but not access the deduplicated data.</p><p>After I enabled the data deduplication role service and rebooted, the system recognized the drives had deduplicated data and could access the files without issue.<br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-75164806324518558692021-02-25T18:21:00.005-06:002021-02-26T09:27:20.486-06:00Synchronize membership from an AD group to a Microsoft 365 group<p>A client recently wanted to control access to Microsoft Stream content by using members of an AD security group that is already synchronized into Azure AD. However, Microsoft Stream access can only be controlled by using Microsoft 365 groups (formerly Office 365 groups). This led to identifying how we can synchronize the group membership from the AD group to the Microsoft 365 group.</p><p>The first option I thought of was Windows PowerShell. This is certainly possible, but it would need to be run as a scheduled task with credentials stored securely. Have reliance on an on-premises scheduled task was suboptimal. So, I went searching and found a template name <b>Synchronize an Azure AD Group with an Office 365 Group on a recurring basis</b> in Power Automate (formerly Flow) that is for exactly this purpose.</p><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuMeWu3c1L6rV1i-Nwl8MO7A9n9RjLPmFFb-ikvFKGVfPdmiBmgY3S4ol9Xv0vG9e9FcTOE3rmwEuZmCSxjNk-JEmHdLUi7GFqlErp7pgXXejPF0Wy0_XDMiTDwCXDHHLf8qpZZr3STYOs/s381/0-FlowTemplate.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="297" data-original-width="381" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuMeWu3c1L6rV1i-Nwl8MO7A9n9RjLPmFFb-ikvFKGVfPdmiBmgY3S4ol9Xv0vG9e9FcTOE3rmwEuZmCSxjNk-JEmHdLUi7GFqlErp7pgXXejPF0Wy0_XDMiTDwCXDHHLf8qpZZr3STYOs/s320/0-FlowTemplate.png" width="320" /></a></div> <p></p><p>At a high level, this is what the flow does:</p><ul style="text-align: left;"><li>Sets a schedule for running</li><li>Queries membership from a source group</li><li>Queries membership from a target group</li><li>Compares the source and target group membership</li><li>Adds source members members not present in the target group</li><li>Sends a notification email identifying source members that were added <br /></li><li>Identifies target members that are not present in the source group (with the option to provide and exceptions list of target members not to remove)<br /></li><li>Sends an approval request to remove target members that are not present in the source group</li><li>Removes target members that are not present in the source group when the request is approved<br /></li></ul><p>When you add the template it will prompt you for permissions to create connections required to run the flow. The Office 365 Groups and Azure AD connectors require credentials to query and modify group memberships.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicxVLUeyGivs9pJdrQwaivOQpDlXl8vK6GLvYTpzWUco7JR3htGZ2lWtNo0HDQPERJcUD3kWwbJIvOacyZ2R2FsmDeMMyWpJmSrm4O0dCjmEbC0l3rPNl5szJJJClc1p4md8qzg6M22qEA/s951/1-Connections.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="486" data-original-width="951" height="205" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicxVLUeyGivs9pJdrQwaivOQpDlXl8vK6GLvYTpzWUco7JR3htGZ2lWtNo0HDQPERJcUD3kWwbJIvOacyZ2R2FsmDeMMyWpJmSrm4O0dCjmEbC0l3rPNl5szJJJClc1p4md8qzg6M22qEA/w400-h205/1-Connections.png" width="400" /></a></div><p>After you've created the flow from the template there are a few items that you need to configure in the flow. The Recurrence box defines how often the flow runs. The example below is configured to run the flow daily at 2am. <br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHnAFJGkXwS-nnps9WKJYIu7zVNc0evvl5yXJLPzk5dou1G0PWA7WkCBlDQ3S5VtJruTqtSG3z5ULsdImlwAgPJZrI5cWKRxOUYCBxO2bxM3tJuNvJ-4S65MAOclseeooozvO5pASdbIJf/s928/2-recurrance.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="570" data-original-width="928" height="246" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHnAFJGkXwS-nnps9WKJYIu7zVNc0evvl5yXJLPzk5dou1G0PWA7WkCBlDQ3S5VtJruTqtSG3z5ULsdImlwAgPJZrI5cWKRxOUYCBxO2bxM3tJuNvJ-4S65MAOclseeooozvO5pASdbIJf/w400-h246/2-recurrance.png" width="400" /></a> <br /></div><p>Next you need to configure the SourceGroupID and the TargetGroupID. You need to obtain the Object Id attribute for these groups from either the Azure AD admin center or Windows PowerShell. The Object ID for the group is placed in the Value box. There are separate steps for SourceGroupID and TargetGroupID. Effectively, these are populating variables used later in the script.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEig0D_mBt45FQHd8olitaVmU6DPKXgxix5CyQ2WdJEoGb9l2mEnTWdMiEbfJLqWEoinSdANzIDNhNgwIlo_ua9GuRafpHcARr57WA4GBgjN1aW1rJVB98bVlVxL6RNDnZOVXDD0Ue6ONueJ/s930/3-SrcGroupID.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="376" data-original-width="930" height="161" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEig0D_mBt45FQHd8olitaVmU6DPKXgxix5CyQ2WdJEoGb9l2mEnTWdMiEbfJLqWEoinSdANzIDNhNgwIlo_ua9GuRafpHcARr57WA4GBgjN1aW1rJVB98bVlVxL6RNDnZOVXDD0Ue6ONueJ/w400-h161/3-SrcGroupID.png" width="400" /></a></div><p>You also need to define the ApprovedOwnerUPN. This user approves removals from the target group when necessary. Enter the UPN for that user into the Value box.</p><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyC8ieBBFMT-NTe-waejBlqyTjf8yu94tjnJONmSjtkxKaFTjBebFu6Z2_e1TE3ot5QTsbNH2uz8uFGqaD5wdwfjekrr3EAOjSVoplkXY4V0RMeMvLaayqquBKidW4gJ0Qxt8-nw1iWrGv/s938/4-OwnerApprover.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="378" data-original-width="938" height="161" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyC8ieBBFMT-NTe-waejBlqyTjf8yu94tjnJONmSjtkxKaFTjBebFu6Z2_e1TE3ot5QTsbNH2uz8uFGqaD5wdwfjekrr3EAOjSVoplkXY4V0RMeMvLaayqquBKidW4gJ0Qxt8-nw1iWrGv/w400-h161/4-OwnerApprover.png" width="400" /></a></div><p></p><p>Members in the target group that are not members in the source group are removed. If there are some unique members that you want to remain in the target group, you can define them in the ExcludedFromRemove variable. This might be useful for cloud only users such as an admin account.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUh98HVsaV0dn1wHPtMijqMcnV4UvtqsqlvqtjtV941uzmueNC8rdrewy_qUaiyQE8qlt9L2KpuVG5VRUTQatpeFYLwUWfDDDPPhJuBuEWt1oManLFSQ53aZonC0R9qckpp2HIWgRkPiPx/s954/5-ExcludedFromRemove-A.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="392" data-original-width="954" height="164" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUh98HVsaV0dn1wHPtMijqMcnV4UvtqsqlvqtjtV941uzmueNC8rdrewy_qUaiyQE8qlt9L2KpuVG5VRUTQatpeFYLwUWfDDDPPhJuBuEWt1oManLFSQ53aZonC0R9qckpp2HIWgRkPiPx/w400-h164/5-ExcludedFromRemove-A.png" width="400" /></a></div><p>To define the user accounts that are not removed, click on the createArray function in the Value box. This opens a box where you can manually enter the users you don't want to remove. In the screenshot below, susan@contoso.com and jeff@contoso.com are excluded from removal.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEif9tfuDbrX7TZPVRwhlZupR3XIU5Yv3vvCvJMX_3rInVM5ju1BiJhuq65Dx2NUjFjq8o6NhZSFsBhTrAlkPdb6RpN1BVdOZVMGy8Y3_rnE3PDV5ogPqyu1F9woBRH2duGIkTfIMOy9wjIy/s657/6-ExcludedFromRemove-B.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="269" data-original-width="657" height="164" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEif9tfuDbrX7TZPVRwhlZupR3XIU5Yv3vvCvJMX_3rInVM5ju1BiJhuq65Dx2NUjFjq8o6NhZSFsBhTrAlkPdb6RpN1BVdOZVMGy8Y3_rnE3PDV5ogPqyu1F9woBRH2duGIkTfIMOy9wjIy/w400-h164/6-ExcludedFromRemove-B.png" width="400" /></a></div><p>The List source group members and List target group members steps allow you to define a maximum number of results that are returned. By default, this value is 500. You need to define this value large enough to gather the entire membership of each group. Enter the number of group members in the Top box. If you leave this value blank, only 100 results are returned.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHYg39H_zChMw19n_2M1ZgGJ-7ztbfsN1o15syj4kfa9GG7zsIMUZSA5JR4gpKDtVhfwLIcoxIc3UQ4RNpEJuARHjx909JT_KEpjTN8LJW0m88LlaEI0FPTcNZxYWQLzHcK6voY0edxIOc/s960/7-ListSourceGroupMembers-A.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="331" data-original-width="960" height="138" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHYg39H_zChMw19n_2M1ZgGJ-7ztbfsN1o15syj4kfa9GG7zsIMUZSA5JR4gpKDtVhfwLIcoxIc3UQ4RNpEJuARHjx909JT_KEpjTN8LJW0m88LlaEI0FPTcNZxYWQLzHcK6voY0edxIOc/w400-h138/7-ListSourceGroupMembers-A.png" width="400" /></a></div><p></p><p>If your groups have membership higher than 1000 then you need to perform additional configuration to allow more than 1000 results. Click the ellipsis in the List source group members step and then select Settings to get the following screen. To allow more than 1000 results you need to turn on pagination and specify the number of results you want to allow. The example below allows up to 2000 members. After you've done this, the Top value is ignored.<br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM6fdtSXdJP0nVXzGtnqGb2nuBBF16wi2Stqm6Oz29767VvEE1ARFj3fJRxuNc3-HxVeq1jear3FJBgOkWZlyBBfLcCq_tHzKrsei7OjE4MYjLQhR50h3wvoor6CLik1KJ87ZA6pk4jIMr/s953/8-ListSourceGroupMembers-Pagination.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="312" data-original-width="953" height="131" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM6fdtSXdJP0nVXzGtnqGb2nuBBF16wi2Stqm6Oz29767VvEE1ARFj3fJRxuNc3-HxVeq1jear3FJBgOkWZlyBBfLcCq_tHzKrsei7OjE4MYjLQhR50h3wvoor6CLik1KJ87ZA6pk4jIMr/w400-h131/8-ListSourceGroupMembers-Pagination.png" width="400" /></a></div><br />The only other issue I had when using this flow was related to the officeLocation attribute of the user objects in Azure AD. This attribute correlates with the physicalDeliveryOfficeName attribute in on-premises AD and is visible as Office on the General tab of a user in Active Directory Users and Computers.<p></p><p>If the officeLocation attribute is blank, then the flow fails. In the environments I deal with, this value is often blank. Rather than putting in dummy office information, we can edit the flow to ignore the null value. </p><p>The two affected steps are:</p><ul style="text-align: left;"><li><b>Parse UsersAdded values</b> in <b>Send mail if UsersAdded variable is not empty</b></li><li><b>Parse MembersToRemove values</b> in <b>Get approval if UsersToRemove is not empty </b><br /></li></ul><p>Both of these steps have a Schema box that defines how attributes are selected from earlier data. The officeLocation attribute is defined as a string and thus fails when there is a null value. A quick way to fix this problem is by removing officeLocation from the schema. Remove the three lines are highlighted from both steps.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCAd3f4HCG1hMLmhBRHDrQY46p_3csUzR3ru-paLvJ8vf8JGE8Co1zb3sY3Z5E-7TTkPDjPEz7yxKfjAwRa3-_wWnXPakBGM0XSAFr7pCFg7kDzqS-Yd5KJcJxEtNn72AXSwByTJ3eTuBo/s969/9-parseUsersAddedvalues.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="557" data-original-width="969" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhCAd3f4HCG1hMLmhBRHDrQY46p_3csUzR3ru-paLvJ8vf8JGE8Co1zb3sY3Z5E-7TTkPDjPEz7yxKfjAwRa3-_wWnXPakBGM0XSAFr7pCFg7kDzqS-Yd5KJcJxEtNn72AXSwByTJ3eTuBo/w400-h230/9-parseUsersAddedvalues.png" width="400" /></a></div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhbVrMTYN0DUDD3-VANmA9Y1rDWnuIvtp00cyYD7mpqAdgzUh2KdzGN6yIDylX8L-0Kkt6X-0M_IAC8UEEhJpwZ1QAV3GkveY05-2QLofk2fWBzoq1vC3mkprdwqd6M_Zvp_H5CUc30cN_/s945/10-parseMembersToRemovevalues.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="537" data-original-width="945" height="228" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhbVrMTYN0DUDD3-VANmA9Y1rDWnuIvtp00cyYD7mpqAdgzUh2KdzGN6yIDylX8L-0Kkt6X-0M_IAC8UEEhJpwZ1QAV3GkveY05-2QLofk2fWBzoq1vC3mkprdwqd6M_Zvp_H5CUc30cN_/w400-h228/10-parseMembersToRemovevalues.png" width="400" /></a></div><p>An alternative that I haven't tested is modifying the acceptable values for officeLocation. You can see that above officeLocation, the mobilePhone attribute allows the type to be either string or null. I believe that syntax would also work for officeLocation.</p><p>For more information about Power Automate (formerly Flow), see:</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/power-automate/">https://docs.microsoft.com/en-us/power-automate/</a><br /></li></ul><p><br /></p><p><br /></p><p><br /></p><p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com8tag:blogger.com,1999:blog-4105213612144933942.post-24468149005896693372021-02-24T16:17:00.001-06:002021-05-18T11:26:03.354-05:00Cisco AnyConnect blocked in Hyper-V virtual machine <p>Cisco AnyConnect is popular VPN software. The VPN server can enforce policies on the connecting clients. One control is blocking access from remote desktop connections. I assume that this is primarily to block connections from Remote Desktop servers or Windows 10 Remote Desktop where the same computer might be simultaneously shared by multiple users.</p><p>The error message you'll see is:</p><p style="margin-left: 40px; text-align: left;">VPN establishment capability from a remote desktop is disabled. A VPN connection will not be established.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHktmynZ0o3p_-CHnuTAxAeD0hFCB_M_Pw-_P1vP2sJT3dLsCQ9jKME1MZ4Ama6ZwQhVsxXKfZLCgByRsOOF03u3wPncV2yzomYbPC04hjuW0cfP8tfMEFs1jL3C0IAkNAV2hjFC9EYuN8/s910/CiscoError.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="355" data-original-width="910" height="156" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHktmynZ0o3p_-CHnuTAxAeD0hFCB_M_Pw-_P1vP2sJT3dLsCQ9jKME1MZ4Ama6ZwQhVsxXKfZLCgByRsOOF03u3wPncV2yzomYbPC04hjuW0cfP8tfMEFs1jL3C0IAkNAV2hjFC9EYuN8/w400-h156/CiscoError.png" width="400" /></a></div><br />You might see this error when you use Hyper-V Manager to access a virtual machine and run Cisco AnyConnect. By default, the connection to a virtual machine is an enhanced session that is based on RDP. If you disable the <b>Enhanced session</b> setting in the <b>View </b>menu then Cisco AnyConnect will run and connect properly.<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmITJc7tAFtjf_8n-kQ2cp9l-uSjjE5_VpvFlcHPcxk7fJA7UrvfzycLrJwEcqWHT8exnbXZd09CZXoIklQItQGq8KmzznHMb4EP6C-bIoD9szSObIDf5fOhbnGHGaW2qRYJT6SjELLhr6/s410/EnhancedSession.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="185" data-original-width="410" height="180" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmITJc7tAFtjf_8n-kQ2cp9l-uSjjE5_VpvFlcHPcxk7fJA7UrvfzycLrJwEcqWHT8exnbXZd09CZXoIklQItQGq8KmzznHMb4EP6C-bIoD9szSObIDf5fOhbnGHGaW2qRYJT6SjELLhr6/w400-h180/EnhancedSession.png" width="400" /></a></div><br /><p><br /></p><p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-80015911928955902532021-02-24T16:03:00.004-06:002021-02-24T16:03:49.534-06:00What is Azure AD Domain Services?<p>Sometimes letting go of old information and assumptions is the hardest part of learning something new. That's what I ran into today trying to wrap my head around Azure AD Domain Services. To purge your brain, let me start by saying that Azure AD Domain Services does not behave like an off site domain controller for your on-premises deployment of Active Directory Domain Services (AD DS).</p><p>Some quick definitions:</p><ul style="text-align: left;"><li>Active Directory Domain Services (AD DS) - Commonly called Active Directory, this is your local directory service/domain. You have domain controllers for this domain. This domain holds user and computer objects.</li><li>Azure Active Directory - Commonly called Azure AD, this is the cloud directory service used for Microsoft cloud services such as Exchange Online and SharePoint Online.</li><li>Azure AD Connect - This is software that you run on-premises to synchronize users and groups from AD DS on-premises to Azure AD. This allows your users to sign in to Microsoft cloud services by using the same username and password as the local AD.</li></ul><p>Azure AD Domain Services (Azure AD DS) is a limited version AD DS that is provided as a cloud service. Like AD DS, it can have user and computer accounts.</p><p>The main use case for Azure AD DS is hosting line of business applications in Azure. The virtual machines in Azure can be joined to Azure AD DS and managed by using Group Policy. You have the ability to create organizational units to organize the VMs.</p><p><b>Note</b>: Azure AD DS is a separate domain and not directly linked to your on-premises AD DS.</p><p>To simplify user access to resources joined to Azure AD DS, users credentials are the same as those in on-premises AD DS. The UPN and password are the same in both environments. The user accounts are synchronized as follows:</p><ul style="text-align: left;"><li>On-premises AD DS --> Azure AD Connect</li><li>Azure AD Connect --> Azure AD</li><li>Azure AD --> Azure AD DS<br /></li></ul><p>Implementing Azure AD DS avoids the need to create a VPN from on-premises to Azure to support hosting a domain controller in Azure. It also avoids the need to manage domain controllers in Azure.</p><p>More information:</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/azure/active-directory-domain-services/overview">https://docs.microsoft.com/en-us/azure/active-directory-domain-services/overview</a><br /></li></ul>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0tag:blogger.com,1999:blog-4105213612144933942.post-21800696057274347852021-02-09T13:47:00.005-06:002021-02-19T11:11:48.297-06:00OAuth Certificates with Hybrid Exchange<p>Older versions of Microsoft Exchange in a hybrid configuration with Exchange Online (EXO) used a federation trust to authenticate connections for free/busy information. Newer hybrid deployments of Exchange 2016/2019 use OAuth authentication instead of federation.</p><p>OAuth authentication is reliant on the Auth certificate in your on-premises Exchange. This certificate is created automatically with a lifetime of 5 years when you install Exchange Server on-premises. If this certificate has been replaced, then you also need to update Azure AD with the new certificate information. The simplest way to update the information is by running the hybrid wizard again after you update the Auth certificate.</p><p>I wrote a previous post about renewing/updating the Exchange Server Auth certificate here:</p><ul style="text-align: left;"><li><a href="http://byronwright.blogspot.com/2018/05/expired-microsoft-exchange-server-auth.html">http://byronwright.blogspot.com/2018/05/expired-microsoft-exchange-server-auth.html</a><br /></li></ul><p>If you update the Exchange Server Auth certificate and forget to update the information in Azure AD, you are likely to see free/busy lookups to EXO fail. I recently saw this as a client and decided to dig into the configuration a little bit more.</p><h2 style="text-align: left;">Testing OAuth Connectivity</h2><p>You can test OAuth authentication from the Exchange Management Shell on-premises. When doing this, you need specify a local mailbox with the following command:</p>
<!-- HTML generated using hilite.me --><div style="background: #f0f0f0; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;color: #000000;"><pre style="margin: 0; line-height: 125%"><span style="color: #007020">Test-OAuthConnectivity</span> -Service EWS -TargetUri <span style="color: #4070a0">'https://outlook.office365.com/ews/exchange.asmx'</span> -Mailbox user@domain.com -Verbose | FL
</pre></div>
<p>With the FL in the above command you'll see detailed information returned. If you run the command without FL and it's successful, you'll see output like this:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLupvNoTJgh41xgVscrWdyii2FYMoRmQkJwT-3A3_07iTqgEJBCBIiEzxvAMcBSINqEZFzgkBYjMtFD-1sGC7Yd9tMmV_NHkAm147f10R13tYZgybud9VmPpRf80vd8MiygNzmanGX485a/s542/TestOAuthConnectivity.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="117" data-original-width="542" height="86" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLupvNoTJgh41xgVscrWdyii2FYMoRmQkJwT-3A3_07iTqgEJBCBIiEzxvAMcBSINqEZFzgkBYjMtFD-1sGC7Yd9tMmV_NHkAm147f10R13tYZgybud9VmPpRf80vd8MiygNzmanGX485a/w400-h86/TestOAuthConnectivity.png" width="400" /></a></div><p> </p><p>If the test is unsuccessful, you'll see text something like:<br /></p><ul style="text-align: left;"><li>The remote server returned an error: (401) Unauthorized <br /></li><li>Unable to get the token from Auth Server</li><li>Client assertation contains an invalid signature [Reason - The key was not found]<br /></li></ul><h2 style="text-align: left;">Verifying the Certificate Used for OAuth <br /></h2><p>To identify the Auth certificate currently used by on-premises Exchange Server, run <b>Get-AuthConfig</b>. In the example below, you can see the thumbprint for the currently used certificate. If the Auth certificate had been updated, it may show a previous certificate thumbprint too. The service name returned by this command is a GUID for Exchange Online.<br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNEEl3pMGWaQvWAmnEhDJ09a92IuLBWfFNeh4LM7R9cRykyQzdRHI9yn7qtLGpEoeavWBHB98Al1uyS7w6H27SMmCbIhnfC8O6V2q_ee1QzmQAjMexRc-vZVpfOkkObcZGsqGtu52QeqbH/s898/GetAuthConfig.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="252" data-original-width="898" height="113" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNEEl3pMGWaQvWAmnEhDJ09a92IuLBWfFNeh4LM7R9cRykyQzdRHI9yn7qtLGpEoeavWBHB98Al1uyS7w6H27SMmCbIhnfC8O6V2q_ee1QzmQAjMexRc-vZVpfOkkObcZGsqGtu52QeqbH/w400-h113/GetAuthConfig.png" width="400" /></a></div><br /><p>Once you have the thumbprint of the Auth certificate, you can verify it exists on each of the on-premises Exchange servers. All servers should have the same Auth certificate. Use the following command for each of your on-premises Exchange servers.</p>
<!-- HTML generated using hilite.me --><div style="background: #f0f0f0; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;color: #000000;"><pre style="margin: 0; line-height: 125%"><span style="color: #007020">Get-ExchangeCertificate</span> ThumbprintFromAuthConfig -Server ServerName
</pre></div>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizNhnkY5WyZufiJ7CyMe-Q9J0p9Vpqnp9rMsdveSs7m3NjOxbaE1B3uRDyfL_8be0ovdI1LJC56QD0YRrPb5mvcxSUhkGWDuZnvqGEA-TX4m1OYUbCKon4vPtVAA3hOaF_BykdnqjTO6yy/s1296/GetExchangeCertificate.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="157" data-original-width="1296" height="49" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizNhnkY5WyZufiJ7CyMe-Q9J0p9Vpqnp9rMsdveSs7m3NjOxbaE1B3uRDyfL_8be0ovdI1LJC56QD0YRrPb5mvcxSUhkGWDuZnvqGEA-TX4m1OYUbCKon4vPtVAA3hOaF_BykdnqjTO6yy/w400-h49/GetExchangeCertificate.png" width="400" /></a></div><br /><p></p><h2 style="text-align: left;">Verifying Certificate Information in Azure AD</h2><p style="text-align: left;">The Auth certificate configured in on-premises Exchange Server is used for client authentication to Azure AD for free/busy lookups. The public portion of the certificate is stored in Azure AD for this purpose. You can view this information by using the MSOL or AzureAD cmdlets.</p><p style="text-align: left;">To view the certificate information with the MSOL cmdlets, run the following command:</p>
<!-- HTML generated using hilite.me --><div style="background: #f0f0f0; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;color: #000000;"><pre style="margin: 0; line-height: 125%"><span style="color: #007020">Get-MsolServicePrincipalCredential</span> -ServicePrincipalName <span style="color: #4070a0">"00000002-0000-0ff1-ce00-000000000000"</span> -ReturnKeyValues <span style="color: #bb60d5">$true</span>
</pre></div>
<p style="text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqJXOaXRUCZJbkQCNbvIzjP81lROy_L6K0lS-9JRo8pgz-YfioZ4ccpGIGT_pbntkMB-V5YX8KElng4CtZXEcgLUu9BY8aJkpLRnRSVWyFsx8ruZiwoGCcj-n4eFKhMCwrmDdotUXYLK1P/s1200/GetMsolServicePrincipalCredential.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="443" data-original-width="1200" height="148" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqJXOaXRUCZJbkQCNbvIzjP81lROy_L6K0lS-9JRo8pgz-YfioZ4ccpGIGT_pbntkMB-V5YX8KElng4CtZXEcgLUu9BY8aJkpLRnRSVWyFsx8ruZiwoGCcj-n4eFKhMCwrmDdotUXYLK1P/w400-h148/GetMsolServicePrincipalCredential.png" width="400" /></a></p>This command may return multiple results. In my test environment, there are multiple entries for the same certificate. I assume that this is because it gets added each time I run the hybrid wizard.<p></p><p>The easiest way to verify that the certificate in Azure AD matches your on-premises Auth certificate is by using the StartDate and EndDate fields. These will match the NotBefore and NotAfter properties shown by Get-ExchangeCertificate. Be aware that StartDate and EndDate are in UTC time and the NotBefore and NotAfter are probably showing in your local time zone.<br /></p><p>You can also save the Value property in a text file with the extension .cer. Then you can open the .cer file and view the certificate information. This includes additional information such as the Thumbprint which you can verify against the on-premises Auth certificate.<br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4HFkseJzLz3tOsG24Sse1ltqV4xxmv3GjsfKkrug5gzYH-mjM2gCCqXTJsinsLybDf3b9tpoT7th5HPXXTNVBNDiH7baUPWPYEc8B8UIsxeudQfLFEFyq3g9X83_K6T48fUxrVvR2aGuU/s773/AuthCert.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="773" data-original-width="607" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4HFkseJzLz3tOsG24Sse1ltqV4xxmv3GjsfKkrug5gzYH-mjM2gCCqXTJsinsLybDf3b9tpoT7th5HPXXTNVBNDiH7baUPWPYEc8B8UIsxeudQfLFEFyq3g9X83_K6T48fUxrVvR2aGuU/w314-h400/AuthCert.png" width="314" /></a></div> <p></p><p>You can use the Azure AD cmdlets to get similar information (but not the certificate value). This is a bit more complex and requires multiple steps.<br /></p><p>To get the ObjectID of the SPN for Exchange online:</p>
<!-- HTML generated using hilite.me --><div style="background: #f0f0f0; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;color: #000000;"><pre style="margin: 0; line-height: 125%"><span style="color: #bb60d5">$spnID</span> = (<span style="color: #007020">Get-AzureADServicePrincipal</span> | Where DisplayName <span style="color: #666666">-eq</span> <span style="color: #4070a0">'Office 365 Exchange Online'</span>).ObjectID
</pre></div>
<p style="text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjn5bUmpptW6YRyShyphenhyphenqpgA1bcehZ7CA0L_Gh4ZTPrL8v95ceDSxGbCuwEytcf5XbkCVYUMyUQpedmCTMEw9kYbzmF-ynKKq-HWaehG8wrlOQXTcEjAzx6pvgi4MsW86Zp_Rh4l8FYWKAluL/s1201/SPNid.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="247" data-original-width="1201" height="83" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjn5bUmpptW6YRyShyphenhyphenqpgA1bcehZ7CA0L_Gh4ZTPrL8v95ceDSxGbCuwEytcf5XbkCVYUMyUQpedmCTMEw9kYbzmF-ynKKq-HWaehG8wrlOQXTcEjAzx6pvgi4MsW86Zp_Rh4l8FYWKAluL/w400-h83/SPNid.png" width="400" /></a></p><br /><p></p><p>To list the certificates that have been uploaded:</p><p></p>
<!-- HTML generated using hilite.me --><div style="background: #f0f0f0; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;color: #000000;"><pre style="margin: 0; line-height: 125%"><span style="color: #bb60d5">$certs</span> = <span style="color: #007020">Get-AzureADServicePrincipalKeyCredential</span> -ObjectID <span style="color: #bb60d5">$spnID</span>
</pre></div>
<p></p><p style="text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1xGji2pgN-7_kcBlQL7fgFiD8Q9PS9Kv9KMM5QPOu1Qwfe4lXlmRhhEMfd6fF44OIhgXg1TMxOrHlbEcJctV8ZDamAcfNpzOjQH079bu5bSDS2SC8yt02sUdkluda2zvDYqpbWZXBMWq_/s983/Certs0.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="260" data-original-width="983" height="106" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1xGji2pgN-7_kcBlQL7fgFiD8Q9PS9Kv9KMM5QPOu1Qwfe4lXlmRhhEMfd6fF44OIhgXg1TMxOrHlbEcJctV8ZDamAcfNpzOjQH079bu5bSDS2SC8yt02sUdkluda2zvDYqpbWZXBMWq_/w400-h106/Certs0.png" width="400" /></a></p><br /><p></p><p>To get the thumbprint of the most recent certificate uploaded:</p>
<!-- HTML generated using hilite.me --><div style="background: #f0f0f0; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;color: #000000;"><pre style="margin: 0; line-height: 125%"><span style="color: #60add5">[system.convert]</span>::ToBase64String(<span style="color: #bb60d5">$certs</span>[0].CustomKeyIdentifier)
</pre></div>
<p style="text-align: center;"> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg7VYIEdRjUk2W4LqNPQnu-_eHL0F9VSUp-p-_2RcQsknRR-sImxRLubaVv6T83LxsdQETc7UvXTuXkGSXN__WR6hWCdA9-3jIFu3nM1nrnfawJwpL4sLXyh1Z5FWBsV3Z6KI9qWbkQTll/s892/thumprint.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="76" data-original-width="892" height="34" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg7VYIEdRjUk2W4LqNPQnu-_eHL0F9VSUp-p-_2RcQsknRR-sImxRLubaVv6T83LxsdQETc7UvXTuXkGSXN__WR6hWCdA9-3jIFu3nM1nrnfawJwpL4sLXyh1Z5FWBsV3Z6KI9qWbkQTll/w400-h34/thumprint.png" width="400" /></a></p><p></p><h2 style="text-align: left;"> Update Auth Certificate in Azure AD<br /></h2><p>If you find that the on-premises auth certificate is not present in Azure AD, the best solution is running the hybrid wizard again. Running the hybrid wizard will update the OAuth certificate.</p><p>If for some reason you don't want to run the hybrid wizard, you can update the certificate manually by exporting it from on-premises and importing it into Azure AD.</p><p>Steps 3 and 4 in the following document describe how to manually export and import the Auth certificate:</p><ul style="text-align: left;"><li><a href="https://docs.microsoft.com/en-us/exchange/configure-oauth-authentication-between-exchange-and-exchange-online-organizations-exchange-2013-help#step-3-export-the-on-premises-authorization-certificate">https://docs.microsoft.com/en-us/exchange/configure-oauth-authentication-between-exchange-and-exchange-online-organizations-exchange-2013-help#step-3-export-the-on-premises-authorization-certificate</a></li></ul><h2 style="text-align: left;">Links found during research<br /></h2><ul style="text-align: left;"><li><a href="https://newsignature.com/articles/on-premises-mailbox-cannot-see-free-busy-information-for-exchange-online-mailbox/">https://newsignature.com/articles/on-premises-mailbox-cannot-see-free-busy-information-for-exchange-online-mailbox/</a></li><li><a href="https://social.technet.microsoft.com/Forums/en-US/a0bb1d0a-6639-4fdf-92e1-7fa350e47057/exchange-2016-onpremises-users-are-not-able-to-view-calendar-information-of-the-office-365-users?forum=exchangesvrdevelopment">https://social.technet.microsoft.com/Forums/en-US/a0bb1d0a-6639-4fdf-92e1-7fa350e47057/exchange-2016-onpremises-users-are-not-able-to-view-calendar-information-of-the-office-365-users?forum=exchangesvrdevelopment</a></li><li><a href="https://techcommunity.microsoft.com/t5/exchange-team-blog/demystifying-hybrid-free-busy-finding-errors-and-troubleshooting/ba-p/607727">https://techcommunity.microsoft.com/t5/exchange-team-blog/demystifying-hybrid-free-busy-finding-errors-and-troubleshooting/ba-p/607727</a></li><li><a href="https://techcommunity.microsoft.com/legacyfs/online/media/2019/01/FB_Errors.FixesV6.pdf">https://techcommunity.microsoft.com/legacyfs/online/media/2019/01/FB_Errors.FixesV6.pdf</a></li></ul><p> </p><p><br /></p>Byron Wrighthttp://www.blogger.com/profile/07966925685909036913noreply@blogger.com0