<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jānis Veinbergs</title>
    <description>The latest articles on DEV Community by Jānis Veinbergs (@janisveinbergs).</description>
    <link>https://dev.to/janisveinbergs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F186405%2F55ab3bf1-0a32-42a3-9074-14de38d3fdf9.jpeg</url>
      <title>DEV Community: Jānis Veinbergs</title>
      <link>https://dev.to/janisveinbergs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/janisveinbergs"/>
    <language>en</language>
    <item>
      <title>Using Dataverse Service Client to connect to OnPrem Dynamics 365 CRM (From .NET 6+)</title>
      <dc:creator>Jānis Veinbergs</dc:creator>
      <pubDate>Wed, 30 Aug 2023 11:51:11 +0000</pubDate>
      <link>https://dev.to/janisveinbergs/using-dataverse-service-client-to-connect-to-onprem-dynamics-365-crm-from-net-6-2ic4</link>
      <guid>https://dev.to/janisveinbergs/using-dataverse-service-client-to-connect-to-onprem-dynamics-365-crm-from-net-6-2ic4</guid>
      <description>&lt;p&gt;Can you use new Dataverse ServiceClient &lt;br&gt;
 (&lt;a href="https://www.nuget.org/packages/Microsoft.PowerPlatform.Dataverse.Client/" rel="noopener noreferrer"&gt;Microsoft.PowerPlatform.Dataverse.Client&lt;/a&gt;) with .NET 6+ for for your console app to connect to on-premises instance of Dynamics 365 CRM? Yes you can! Here is the &lt;a href="https://learn.microsoft.com/en-us/power-apps/developer/data-platform/xrm-tooling/use-connection-strings-xrm-tooling-connect" rel="noopener noreferrer"&gt;connection string&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AuthType=OAuth;Url=https://org.crm.example.com/;Username=user@example.com;Password=&amp;lt;pw&amp;gt;;RedirectUri=http://localhost:54321;AppId=174697c7-4ec8-401a-88ce-e6af4d05b6dd;LoginPrompt=Auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, after you appropriately configure ADFS and CRM, that is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intro and Supportability
&lt;/h2&gt;

&lt;p&gt;There are 2 Client SDKs that abstract API calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long time CRM Service Client SDK (&lt;a href="https://www.nuget.org/packages/Microsoft.CrmSdk.XrmTooling.CoreAssembly/#supportedframeworks-body-tab" rel="noopener noreferrer"&gt;Microsoft.CrmSdk.XrmTooling.CoreAssembly&lt;/a&gt;) windows-only, supports .NET Framework 4.6.2+, but not .NET Core world, thus not cross-platform. Still supported.&lt;/li&gt;
&lt;li&gt;Dataverse ServiceClient 
(&lt;a href="https://www.nuget.org/packages/Microsoft.PowerPlatform.Dataverse.Client/" rel="noopener noreferrer"&gt;Microsoft.PowerPlatform.Dataverse.Client (DVSC)&lt;/a&gt;) - .NET 6, cross platform. Moving away from SOAP, but still uses it. No guidance from Microsoft to use for on-premises, so at the time of writing can be considered unsupported. &lt;a href="https://learn.microsoft.com/en-us/power-apps/developer/data-platform/sdk-client-transition#on-premises-clients" rel="noopener noreferrer"&gt;Transition apps to Dataverse ServiceClient&lt;/a&gt; article specifically has a note for on-premises clients that says:
&amp;gt; Leave your application projects and code as is. Continue using the Microsoft.CrmSdk.CoreAssemblies NuGet package and CrmServiceClientclass. However, plan to update your projects from using any custom service clients to instead use the CrmServiceClient or ServiceClient in the near future. See the planned timeline for 2011 SOAP endpoint shutdown below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So are we stuck with .NET Framework for on-premises client applications?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/microsoft/PowerPlatform-DataverseServiceClient" rel="noopener noreferrer"&gt;DVSC source&lt;/a&gt; has bits and pieces, logic and variables like &lt;code&gt;isOnPrem&lt;/code&gt; that indicates that maybe there is light at the end of the tunnel? And the note you just read actually says "... &lt;strong&gt;plan to update&lt;/strong&gt; ... to instead use the &lt;code&gt;CrmServiceClient&lt;/code&gt; or &lt;code&gt;ServiceClient&lt;/code&gt; in the near future.". But maybe that "plan to update" means only plan to update the respective package, when they will get rid of all the SOAP requests in both and use WebAPI purely.&lt;/p&gt;

&lt;p&gt;However it turns out, it is possible to connect to on-premises instance with DVSC if you are using ADFS2019. And in a different way, if you are using ADFS2016.&lt;/p&gt;

&lt;p&gt;I may note that whatever is not officially documented may be considered an unsupported scenario. Things to consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Online, Microsoft has so tightly integrated into Azure... I personally don't expect much changes for on-premises. And we really haven't seen much love there lately.&lt;/li&gt;
&lt;li&gt;With these changes we are actually moving in a direction where Microsoft stands today: MSAL. So would be actually easier to move to online afterwards.&lt;/li&gt;
&lt;li&gt;Nothing unsupported is being done for CRM/ADFS side configuration. Sans 1 thing to overcome current bug in DVSC that will hopefully be fixed.&lt;/li&gt;
&lt;li&gt;Worst that could happen is some new version breaks something for on-premises and no one cares to fix it and we are stuck with older version of DVSC. It is a valid concern, of course, as we may be left stranded with some known vulnerabilities. But for me this is a low probability because of the &lt;em&gt;plan to update&lt;/em&gt; note.
So your decision whether to use .NET 6/DVSC or not in a production environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuring CRM/ADFS 2019
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;You must have: Windows Server 2019+/ADFS 2019+&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.microsoft.com/en-us/topic/march-26-2019-kb4490481-os-build-17763-402-c323e5c1-d524-dbdb-04a0-c3b5c8c8f2fd" rel="noopener noreferrer"&gt;KB4490481&lt;/a&gt; must be installed. Required for &lt;a href="https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/ADFS-support#case-where-msal-connects-directly-to-adfs" rel="noopener noreferrer"&gt;MSAL support&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/deploy/configure-the-dynamics-365-server-for-claims-based-authentication?view=op-9-1" rel="noopener noreferrer"&gt;Configure the Microsoft Dynamics 365 Server for claims-based authentication&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/deploy/configure-the-dynamics-365-server-for-ifd?view=op-9-1" rel="noopener noreferrer"&gt;also for IFD&lt;/a&gt;. Don't forget to do the appropriate configuration on AD FS server for &lt;a href="[Configure%20the%20AD%20FS%20server%20for%20claims-based%20authentication%20|%20Microsoft%20Learn](https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/deploy/configure-the-ad-fs-server-for-claims-based-authentication?view=op-9-1)"&gt;claims&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/deploy/configure-the-ad-fs-server-for-ifd?view=op-9-1" rel="noopener noreferrer"&gt;IFD auth&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I will assume the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ADFS is at &lt;a href="https://adfs.example.com" rel="noopener noreferrer"&gt;https://adfs.example.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CRM non-IFD URL for particular org is at &lt;a href="https://crm.example.com/ORG" rel="noopener noreferrer"&gt;https://crm.example.com/ORG&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;IFD URL for org is at: &lt;a href="https://org.crm.example.com" rel="noopener noreferrer"&gt;https://org.crm.example.com&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See IFD configuration notes at the end of the article.&lt;/p&gt;

&lt;p&gt;Regarding non-IFD, Claims-only configuration for internal access - I tested it with DVSC and as of writing, it didn't work. It did get the access token, but then failed to send requests to right URL - it didn't append the &lt;code&gt;/org&lt;/code&gt; after the URL host part. There is an open &lt;a href="https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/397" rel="noopener noreferrer"&gt;issue 397: non-IFD URLs not handled correctly for on-Prem&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On ADFS Server you have to tell that you have to register your application. You will use &lt;code&gt;ClientId&lt;/code&gt; in connection string. And you have to give permissions for this application to get access token for CRM IFD relying party.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;RedirectUri&lt;/code&gt; &lt;a href="https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/System-Browser-on-.Net-Core#limitations" rel="noopener noreferrer"&gt;must contain port&lt;/a&gt; and must use http protocol (for localhost). If port will not be specified, random will be used by MSAL when you connect via DVSC, but ADFS just won't accept it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;# MSAL will listen on localhost this particular port to get answer from ADFS (authorization code), but that is only true for authorization code grant flow: https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/overview/ad-fs-openid-connect-oauth-flows-scenarios#authorization-code-grant-flow&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="c"&gt;# As for name, in real world, that would be something like: "Accounting Integration" or "Postman" or whatever that communicates the software or intent for the application that is connecting to CRM&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Add-AdfsClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ClientId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;guid&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DVSC Client"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-RedirectUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:54321"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dataverse Service Client will connect to CRM"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ClientType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;Public&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ClientId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-AdfsClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DVSC Client"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ClientId&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="c"&gt;# If you don't grant permissions, you will get error along with an event id 1021 in AD FS event log: MSIS9605: The client is not allowed to access the requested resource. when trying to issue token at /adfs/oauth2/token.&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="c"&gt;# For ServerRoleIdentifier, "external domain" URL must be used you chose when you configured IFD. You inputted 4 domains/urls, this would be the 4th one.&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="c"&gt;# Another way to find out - go to ADFS, open Relying parties, open the IFD relying party (auth.crm.example.com), open Identities, and pick the first one: https://auth.crm.example.com/&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Grant-AdfsApplicationPermission&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ClientRoleIdentifier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ClientId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ServerRoleIdentifier&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://auth.crm.example.com/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ScopeNames&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openid"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ClientId&lt;/span&gt;&lt;span class="w"&gt;

   &lt;/span&gt;&lt;span class="mi"&gt;1260534&lt;/span&gt;&lt;span class="n"&gt;b-00ca-4663-870f-d77b8d4ad6d3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does &lt;em&gt;openid&lt;/em&gt; has to do anything with this? &lt;a href="[AD%20FS%20OpenID%20Connect/OAuth%20concepts%20|%20Microsoft%20Learn](https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/development/ad-fs-openid-connect-oauth-concepts#scopes)"&gt;It means&lt;/a&gt; that you allow your client application to use OpenID Connect (OIDC) authentication protocol.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/deploy/post-installation-configuration-guidelines-dynamics-365?view=op-9-1#configure-windows-server-for-dynamics-365-customer-engagement-on-premises-applications-that-use-oauth" rel="noopener noreferrer"&gt;OAuth must be enabled for CRM&lt;/a&gt;. Execute this on CRM server. Changes will be applied immediately:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Add-PSSnapin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Crm.PowerShell&lt;/span&gt;&lt;span class="w"&gt;  
   &lt;/span&gt;&lt;span class="nv"&gt;$ClaimsSettings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-CrmSetting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SettingType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OAuthClaimsSettings&lt;/span&gt;&lt;span class="w"&gt;  
   &lt;/span&gt;&lt;span class="nv"&gt;$ClaimsSettings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;  
   &lt;/span&gt;&lt;span class="n"&gt;Set-CrmSetting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Setting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ClaimsSettings&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to validate OAuth is enabled, you must open: &lt;a href="https://org.crm.example.com/XRMServices/2011/Organization.svc/web" rel="noopener noreferrer"&gt;https://org.crm.example.com/XRMServices/2011/Organization.svc/web&lt;/a&gt; and see with, say, Fiddler, whether you get &lt;code&gt;WWW-Authenticate&lt;/code&gt; header that is like: &lt;code&gt;Bearer redirect_uri=https://adfs.example.com/adfs/ls/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You may get multiple headers, like &lt;code&gt;WWW-Authenticate: Negotiate&lt;/code&gt; and &lt;code&gt;WWW-Authenticate: NTLM&lt;/code&gt;, but one with &lt;code&gt;Bearer&lt;/code&gt; must be present. If you don't want to configure fiddler to catch TLS requests, you can check what headers that request returns like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://org.crm.exmple.com/XRMServices/2011/Organization.svc/web"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-UseBasicParsing&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exception&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"WWW-Authenticate"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

   &lt;/span&gt;&lt;span class="n"&gt;Bearer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authorization_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;https://adfs.example.com/adfs/oauth2/authorize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resource_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;https://org.crm.example.com/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't worry, the &lt;code&gt;Invoke-WebRequest&lt;/code&gt; returns 401 error - it is expected.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;EDIT: There are actually some required steps documented after enabling OAuth. Particularly removing windows auth from 3 folders. Read &lt;a href="https://learn.microsoft.com/en-us/dynamics365/customerengagement/on-premises/deploy/post-installation-configuration-guidelines-dynamics-365?view=op-9-1#required-steps-after-enabling-oauth-for-dynamics-365-server" rel="noopener noreferrer"&gt;Required steps after enabling OAuth for Dynamics 365 Server&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The previous note:&lt;br&gt;
  As of writing, the DVSC will fail to connect if you get multiple &lt;code&gt;WWW-Authenticate&lt;/code&gt; headers. It is due to a bug in the client itself and hopefully it gets fixed. &lt;a href="https://github.com/microsoft/PowerPlatform-DataverseServiceClient/issues/396" rel="noopener noreferrer"&gt;isOnPrem not passed down correctly when invoking GetAuthorityFromTargetServiceAsync · Issue #396 · microsoft/PowerPlatform-DataverseServiceClient (github.com)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thus, at the time of writing, we need additional configuration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;C:\windows\system32\inetsrv\config\applicationHost.config&lt;/code&gt; on CRM server.&lt;/li&gt;
&lt;li&gt;Find this XML element:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;       &lt;span class="nt"&gt;&amp;lt;location&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft Dynamics CRM/XRMServices/2011/Organization.svc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;system.webServer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;security&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;authentication&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;digestAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;basicAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;anonymousAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;windowsAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/authentication&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/security&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/system.webServer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/location&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Change windowsAuthentication to false. After save, changes are applied immediatelly, no IIS restart was needed.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;       &lt;span class="nt"&gt;&amp;lt;location&lt;/span&gt; &lt;span class="na"&gt;path=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft Dynamics CRM/XRMServices/2011/Organization.svc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;system.webServer&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;security&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;authentication&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;digestAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;basicAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;anonymousAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;windowsAuthentication&lt;/span&gt; &lt;span class="na"&gt;enabled=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/authentication&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/security&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/system.webServer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/location&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Connect with DVSC
&lt;/h2&gt;

&lt;p&gt;I actually uploaded various scenarios to GitHub: &lt;a href="https://github.com/janis-veinbergs/DataverseServiceClientOnPremSamples" rel="noopener noreferrer"&gt;DataverseServiceClientOnPremSamples: Examples on how to use Dataverse Service Client to connect to Dynamics 365 CRM on-premises (github.com)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;appsettings.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ConnectionStrings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AuthType=OAuth;Url=https://org.crm.example.com/;Username=user@example.com;Password=&amp;lt;pw&amp;gt;;AppId=174697c7-4ec8-401a-88ce-e6af4d05b6dd;LoginPrompt=Never"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Logging"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"LogLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Trace"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Program.cs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Crm.Sdk.Messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Logging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.PowerPlatform.Dataverse.Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Xrm.Sdk.Query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;ADFS2019ConnectionString&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoggerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsole&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;CreateLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DATAVERSE_APPSETTINGS"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"appsettings.json"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigurationBuilder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AddJsonFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Program&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;connectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ServiceClient&lt;/span&gt; &lt;span class="n"&gt;serviceClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;WhoAmIResponse&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WhoAmIResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;serviceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WhoAmIRequest&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User ID is {0}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;systemuser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"systemuser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ColumnSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fullname"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Hello, &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;systemuser&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"fullname"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s"&gt;, where do you want to go today?"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// Pause program execution before resource cleanup.&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Press any key to continue."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;serviceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft.Extensions.Configuration&lt;/li&gt;
&lt;li&gt;Microsoft.Extensions.Configuration.Json&lt;/li&gt;
&lt;li&gt;Microsoft.Extensions.Logging.Console&lt;/li&gt;
&lt;li&gt;Microsoft.PowerPlatform.Dataverse.Client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User ID is e0f638f8-e3a0-44df-a242-8557ed7e0e33.
Hello, John Doe, where do you want to go today?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets look at 2 ways we can use the connection string:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#authorization-code" rel="noopener noreferrer"&gt;Authorization code grant&lt;/a&gt;: &lt;code&gt;AuthType=OAuth;Url=https://org.crm.example.com/;Username=user@example.com;Password=&amp;lt;pw&amp;gt;;RedirectUri=http://localhost:54321;AppId=174697c7-4ec8-401a-88ce-e6af4d05b6dd;LoginPrompt=Always&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;UI required (browser). Takes 2 requests to get access token. MSAL library takes care of listening to particular port specified in RedirectUri, so that when first request goes out to ADFS and you get authorization code, browser on your computer where the app runs can get the response, extract authorization code and issue another request to ADFS to finally get the access token.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc" rel="noopener noreferrer"&gt;Resource Owner Password Credentials grant&lt;/a&gt;: &lt;code&gt;AuthType=OAuth;Url=https://org.crm.example.com/;Username=user@example.com;Password=&amp;lt;pw&amp;gt;;AppId=174697c7-4ec8-401a-88ce-e6af4d05b6dd;LoginPrompt=Never&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You authenticate as particular user. No UI required. Takes 1 request to get access token.&lt;/p&gt;

&lt;p&gt;This flow is considered for Desktop and Mobile application types, however I am going to use it for Service (Daemon) application as it requires no user interaction and I'm not so sure if &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#client-credentials" rel="noopener noreferrer"&gt;Client credentials&lt;/a&gt; flow (which is officially meant for daemon applications) can be supported by on-premises instance.&lt;/p&gt;

&lt;p&gt;Has drawbacks - no MFA support, passwords with leading/trailing whitespace not supported etc. See &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc" rel="noopener noreferrer"&gt;notes&lt;/a&gt; and consider what applies to on-premises.&lt;/p&gt;

&lt;h2&gt;
  
  
  ADFS 2016
&lt;/h2&gt;

&lt;p&gt;Well that was for fancy ADFS 2019 which is in friends with MSAL. But there is another option to authenticate against ADFS 2016. Or in this case, we can use more OAuth authentication flows if we want.&lt;/p&gt;

&lt;p&gt;Luckily, there is &lt;code&gt;ServiceClient&lt;/code&gt; &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.powerplatform.dataverse.client.serviceclient.-ctor?view=dataverse-sdk-latest" rel="noopener noreferrer"&gt;constructor&lt;/a&gt; available that takes &lt;code&gt;tokenProviderFunction&lt;/code&gt; - we can just feed in access token that we got by any means we want.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ServiceClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt; &lt;span class="n"&gt;instanceUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tokenProviderFunction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;useUniqueInstance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will use a plain &lt;code&gt;HttpClient&lt;/code&gt; request to ADFS to get &lt;code&gt;access_token&lt;/code&gt;. As MSAL doesn't support ADFS 2016, one can maybe use ADAL (&lt;a href="https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory" rel="noopener noreferrer"&gt;Microsoft.IdentityModel.Clients.ActiveDirectory&lt;/a&gt;) - however it is deprecated.&lt;/p&gt;

&lt;p&gt;Beware this is a quick and dirty example - no refresh tokens, no checking for slash suffixes (i.e for &lt;code&gt;adfsUrl&lt;/code&gt; variable), no correct naming for &lt;code&gt;TokenResponse&lt;/code&gt; class, bare exceptions, no error handling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Crm.Sdk.Messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Configuration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Logging&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.PowerPlatform.Dataverse.Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Xrm.Sdk.Query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;ADFS2016&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LoggerFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddConsole&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;CreateLogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="nf"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DATAVERSE_APPSETTINGS"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"appsettings.json"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ConfigurationBuilder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AddJsonFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Program&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;crmUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CrmUrl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CrmUrl"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;ServiceClient&lt;/span&gt; &lt;span class="n"&gt;serviceClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crmUrl&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenProviderAdfs2016&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;WhoAmIResponse&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WhoAmIResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;serviceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;WhoAmIRequest&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"User ID is {0}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;systemuser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"systemuser"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ColumnSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fullname"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Hello, &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;systemuser&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"fullname"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s"&gt;, where do you want to go today?"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Press any key to continue."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadKey&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;serviceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// Cache access_token for subsequent requests&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;AccessToken&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;TokenProviderAdfs2016&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;instanceUri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccessToken&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CrmUrl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CrmUrl"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;GetLeftPart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UriPartial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Authority&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;///Construct Resource owner password credentials grant request - username/password enought for auth&lt;/span&gt;
            &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;adfsUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AdfsUrl"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AdfsUrl"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpRequestMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adfsUrl&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/oauth2/token"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Accept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FormUrlEncodedContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"grant_type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"client_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AppId"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AppId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="c1"&gt;//If CrmUrl is https://crm.example.com/org, resource will be https://crm.example.com/. This must match with one of identifiers for relying party for CRM in ADFS when configured either claims auth or IFD.&lt;/span&gt;
                &lt;span class="c1"&gt;//ADFS 2016 requires resurce parameter. ADFS 2019 allows resource within scope parameter. MSAL will use scope parameter.&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"openid"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TokenResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
            &lt;span class="n"&gt;AccessToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Couldn't get access_token"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;TokenResponse&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;access_token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;refresh_token&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I already commited &lt;a href="https://github.com/janis-veinbergs/DataverseServiceClientOnPremSamples/blob/master/ADFS2019DeviceFlow/Program.cs" rel="noopener noreferrer"&gt;ADFS2019DeviceFlow&lt;/a&gt; project example which would implement &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows#device-code" rel="noopener noreferrer"&gt;device code flow&lt;/a&gt;. Ever seen this message for some tools? &lt;code&gt;To sign in, use a web browser to open the page https://adfs.example.com/adfs/oauth2/deviceauth and enter the code PLSSCZGKY to authenticate&lt;/code&gt; - you got covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  IFD Configuration notes
&lt;/h2&gt;

&lt;p&gt;When I configured IFD, I did use slightly different URLs than what is at the documentation. Enabling IFD means you will get URL for each organization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;org1.&amp;lt;web application server domain&amp;gt;&lt;/code&gt; (org1.contoso.com)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;org2.&amp;lt;web application server domain&amp;gt;&lt;/code&gt; (org2.contoso.com)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you choose &lt;code&gt;contoso.com&lt;/code&gt;, or &lt;code&gt;example.com&lt;/code&gt; and if it is the same as your AD domain... well, lets say I'm not a fan of "polluting" DNS. I like to separate things. If any of your org names happen to have a same name as any of your hosts in AD domain or more correctly - if DNS already has such entry that doesn't point to CRM server - well, that URL wont work and no DVSC for you.&lt;/p&gt;

&lt;p&gt;Another reason would be if you have SharePoint and it also uses ADFS, you may get additional issues like &lt;code&gt;MSIS7065: There are no registered protocol handlers on path /adfs/ls/ to process the incoming request.&lt;/code&gt;: &lt;a href="https://support.microsoft.com/en-us/topic/passive-federation-request-fails-when-accessing-an-application-using-ad-fs-and-forms-authentication-after-previously-connecting-to-microsoft-dynamics-crm-also-using-ad-fs-25258e3e-6fd7-4425-d8aa-46f9dc3f63d0" rel="noopener noreferrer"&gt;Passive federation request fails when accessing an application using AD FS and Forms Authentication after previously connecting to Microsoft Dynamics CRM also using AD FS - Microsoft Support&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you may do yourself a favor and choose &lt;code&gt;crm.example.com&lt;/code&gt; as web application server domain. In my case I did the following configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web Application Server Domain: &lt;code&gt;crm.example.com&lt;/code&gt; (You will get &lt;code&gt;org.crm.example.com&lt;/code&gt; URLs for organizations)&lt;/li&gt;
&lt;li&gt;Organization Web Service Domain: &lt;code&gt;crm.example.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Discovery Web Service Domain (Err, hostname): &lt;code&gt;dev.crm.example.com&lt;/code&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbl3xlvsjvudmwkspvk5c.png" alt="Wizard - First page" width="607" height="596"&gt;
&lt;/li&gt;
&lt;li&gt;(On the next page) Enter the external domain where your internet-facing servers are located: &lt;code&gt;auth.crm.example.com&lt;/code&gt; (This URL will be used later when giving ADFS permissions)
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F28a8jrowx7w4lyh8vu2c.png" alt="Wizard - Next Page" width="606" height="598"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regarding certificates, you have 2 options. The DNS is on two levels, but wildcard can only cover one domain level. So you have 2 options now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You now need additional wildcard cert for &lt;code&gt;*.crm.example.com&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;You need all-in-one cert with these SANs:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;org.crm.example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;any other org names&amp;gt;.crm.example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auth.crm.example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dev.crm.example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;crm.example.com&lt;/code&gt; (non-IFD URL, maybe it is even your CRM hostname - wildcard cert doesn't cover this cert so it must be added to SAN anyways)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you go with the first option by already having a cert for crm.example.com and would like to add just another certificate for &lt;code&gt;*.crm.example.com&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add it to local computer certificate store.&lt;/li&gt;
&lt;li&gt;Tell your webserver to use this cert when accessing &lt;code&gt;*crm.example.com:443&lt;/code&gt;. If you execute following command with PowerShell, escape curly braces {} with backticks: &lt;code&gt;`{&lt;/code&gt; and &lt;code&gt;`}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   netsh http add sslcert appid={4dc3e181-e14b-4a21-b022-59fc669b0914} hostnameport=*.crm.example.com:443 certstorename=MY certhash=FF9688D828EEFEA33696CEB61153947623EBF2E1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I copied the appid from &lt;code&gt;netsh http show sslcert ipport=0.0.0.0:443&lt;/code&gt; - but it can be whatever, even &lt;code&gt;[guid]::NewGuid()&lt;/code&gt; if you will.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Also make sure in IIS, Microsoft Dynamics CRM app bindings have empty "Host Name" for https
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqsoj6euzvjq5qdepouz4.png" alt="Host Name settings on IIS" width="659" height="524"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About OAuth
&lt;/h2&gt;

&lt;p&gt;Aren't you confused about different grants/flows/terminology? What to use when? How it works? How can I construct those requests myselff, etc. I was/am. Things start to make sense when I dig deeper. Not so much when I want to just get past authentication. These things helped me in a way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Luckily the first thing that helped me orient is which flows are suitable for which case: &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-authentication-flows" rel="noopener noreferrer"&gt;Authentication flow support in the Microsoft Authentication Library (MSAL) - Microsoft Entra | Microsoft Learn&lt;/a&gt; - there you can click links and follow how exactly they work and how the request must be constructed.&lt;/li&gt;
&lt;li&gt;Now the ClientId or apps or relying parties which I see in ADFS. Think of a ClientId like identifier for your app (doh). A thing that enables you to use OAuth. And there you can give access to some resources (relying parties or applications) - if permission is granted, the application you are trying to access will be satisfied with your token. Otherwise not. Remember &lt;code&gt;Grant-AdfsApplicationPermission&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;💡 Aah, that's what registering an app in Azure AD is about - it is to enable OAuth! Ahh, this is what &lt;code&gt;New-AdfsClient&lt;/code&gt; does, kind of the same!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is Microsoft doing this to us?
&lt;/h2&gt;

&lt;p&gt;Why don't we get WS-Trust or windows integrated authentication? If we consider the following, all of this kind of makes sense: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft.CrmSdk.XrmTooling.CoreAssembly being windows only has access to underlying windows security libs and ADFS via .NET Framework to construct Kerberos / WS-Trust token formats.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/update-your-applications-to-use-microsoft-authentication-library/ba-p/1257363" rel="noopener noreferrer"&gt;ADAL is deprecated&lt;/a&gt; without backward support for ADFS 2016 which is still supported.&lt;/li&gt;
&lt;li&gt;Dynamics 365 CRM on-premises uses WS-Trust protocol which is &lt;a href="https://learn.microsoft.com/en-us/power-platform/important-changes-coming#deprecation-of-office365-authentication-type-and-organizationserviceproxy-class-for-connecting-to-dataverse" rel="noopener noreferrer"&gt;deprecated &lt;/a&gt;by Microsoft, due to various &lt;a href="https://www.proofpoint.com/us/blog/cloud-security/new-vulnerabilities-bypass-multi-factor-authentication-microsoft-365" rel="noopener noreferrer"&gt;vulnerabilities&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;All in all, DVSC being designed to be cross platform, has lost many authentication scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then ADFS 2016 &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-adfs-support#msal-connects-directly-to-ad-fs" rel="noopener noreferrer"&gt;lacking necessary support for MSAL&lt;/a&gt;, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Still relies on &lt;code&gt;resource&lt;/code&gt; parameter (whereas these libraries only use &lt;code&gt;scope&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Doesn't support Proof Key for Code Exchange (PKCE)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So on-premises along with number of authentication requirements and support for ADFS 2016 is supported with &lt;code&gt;Microsoft.CrmSdk.XrmTooling.CoreAssembly&lt;/code&gt; package. And &lt;code&gt;Microsoft.PowerPlatform.Dataverse.Client&lt;/code&gt; supporting modern authentication requirements for cloud environment.&lt;/p&gt;

&lt;p&gt;Helpful literature: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-net-differences-adal-net" rel="noopener noreferrer"&gt;Differences between ADAL.NET and MSAL.NET apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-migration" rel="noopener noreferrer"&gt;Migrate to the Microsoft Authentication Library (MSAL)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the on-premise dinosaurs 🦖 can be modern too, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Other libraries
&lt;/h2&gt;

&lt;p&gt;You may also want to check out &lt;a href="https://github.com/Data8/DataverseClient" rel="noopener noreferrer"&gt;Data8 .NET Core Client SDK for On-Premise Dynamics 365/CRM&lt;/a&gt; - Haven't tried it, but from what I read it supports WS-Trust and Windows Integrated Authentication.&lt;/p&gt;

&lt;p&gt;P.S. 🦖 use the legacy vocabulary. Please don't be bothered too much when I call CRM what is in one way or another a subset/superset for Dataverse, PowerPlatform, Dynamics 365 Customer Engagement/Sales/Marketing, Common Data Service, XRM. 3 characters are the shortest way to reference CRM.&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>crm</category>
      <category>dataverse</category>
      <category>adfs</category>
    </item>
    <item>
      <title>Dynamics CRM / Any ASP.NET App - Improve C# dynamic compilation times</title>
      <dc:creator>Jānis Veinbergs</dc:creator>
      <pubDate>Wed, 19 Apr 2023 08:14:41 +0000</pubDate>
      <link>https://dev.to/janisveinbergs/dynamics-crm-improve-58g9</link>
      <guid>https://dev.to/janisveinbergs/dynamics-crm-improve-58g9</guid>
      <description>&lt;h1&gt;
  
  
  The Pain
&lt;/h1&gt;

&lt;p&gt;Since I installed Dynamics 365 (CRM) v9.1 On-Premises, I get REALLY slow compile times. The &lt;strong&gt;csc.exe (C# Compiler) is so slow&lt;/strong&gt; to compile whenever I make &lt;strong&gt;unique request&lt;/strong&gt;, I could wait for many seconds or minutes for compilation to finish. So I was searching for a way to improve things. It doesn't do parallel compilation if I make multiple unique requests from multiple browser tabs and it doesn't utilize CPU that much. A "&lt;a href="https://stackoverflow.com/a/70342126/50173"&gt;legacy junk&lt;/a&gt;" indeed.&lt;/p&gt;

&lt;p&gt;The ASP.net does dynamic compilation on first request. You can read more about it at &lt;a href="https://learn.microsoft.com/en-us/previous-versions/aspnet/ms366723(v=vs.100)"&gt;Understanding ASP.NET Dynamic Compilation.&lt;/a&gt;. But from Task Manager it looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe" /noconfig /fullpaths @"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\0345f868\a571ab7d\mejffpag.cmdline"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  The Painkillers?
&lt;/h1&gt;

&lt;p&gt;As Dynamics 365 is a 3rd party app and I thought I could at least precompile the aspx pages and such. So I did just that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;. C:\windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_compiler.exe -m "/LM/W3SVC/2/ROOT"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it did take a while to complete, I certainly had good feelings about this. Just to find out csc.exe still doing dynamic compilation when I make requests and the performance for my dev environment hasn't improved at all (just a gut feeling, didn't do measurements). That means I have to take first request hit all of my own for any request. :(&lt;/p&gt;

&lt;p&gt;But it did something, as it generated tons of files within &lt;code&gt;Temporary ASP.NET Files&lt;/code&gt; folder - probably just not enought.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Painkillers!
&lt;/h1&gt;

&lt;p&gt;After a while I stumbled upon something interesting: &lt;a href="https://devblogs.microsoft.com/dotnet/enabling-the-net-compiler-platform-roslyn-in-asp-net-applications/"&gt;Enabling the .NET Compiler Platform (“Roslyn”) in ASP.NET applications&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Eager to try out and do some not-so-much-supported modifications within 3rd party app web.config that will certainly be overwritten when I apply next patch - but it is a dev env, no real risk of breaking it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Got nuget.exe and within arbitrary directory:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;nuget.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.CodeDom.Providers.DotNetCompilerPlatform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;3.6.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutputDirectory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\packages\&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# note that Dynamics 365 (CRM) 9.1 Is .NET Framework 4.6.2 app.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\tools\Roslyn46\*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Program Files\Dynamics 365\CRMWeb\bin\roslyn"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\lib\net45\*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Program Files\Dynamics 365\CRMWeb\bin\"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then edited "C:\Program Files\Dynamics 365\CRMWeb\web.config" by adding &lt;code&gt;&amp;lt;system.codedom&amp;gt;&lt;/code&gt; node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
  ...
  &lt;span class="nt"&gt;&amp;lt;system.codedom&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;compilers&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;compiler&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"c#;cs;csharp"&lt;/span&gt; &lt;span class="na"&gt;extension=&lt;/span&gt;&lt;span class="s"&gt;".cs"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"&lt;/span&gt;
        &lt;span class="na"&gt;warningLevel=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="na"&gt;compilerOptions=&lt;/span&gt;&lt;span class="s"&gt;"/langversion:7.3 /nowarn:1659;1699;1701"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;compiler&lt;/span&gt; &lt;span class="na"&gt;language=&lt;/span&gt;&lt;span class="s"&gt;"vb;vbs;visualbasic;vbscript"&lt;/span&gt; &lt;span class="na"&gt;extension=&lt;/span&gt;&lt;span class="s"&gt;".vb"&lt;/span&gt;
        &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"&lt;/span&gt;
        &lt;span class="na"&gt;warningLevel=&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt; &lt;span class="na"&gt;compilerOptions=&lt;/span&gt;&lt;span class="s"&gt;"/nowarn:41008 /define:_MYTYPE=\&amp;amp;quot;Web\&amp;amp;quot; /optionInfer+"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/compilers&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/system.codedom&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did validate that it was using the new compiler: &lt;code&gt;csc.exe&lt;/code&gt; was from &lt;code&gt;bin/roslyn&lt;/code&gt; directory and I saw a &lt;code&gt;VBCSCompiler.exe&lt;/code&gt; process too. That verifies that web.config changes are correct and assemblies are in correct places.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Restart-Service W3SVC&lt;/code&gt; wasn't needed, as changes to web.config are detected and app pool gets recycled.&lt;/p&gt;

&lt;h1&gt;
  
  
  Performance improvements
&lt;/h1&gt;

&lt;p&gt;So I had to measure performance improvement. I opted for &lt;a href="https://github.com/google/UIforETW"&gt;UIforETW&lt;/a&gt; to initiate hassle free etw trace and &lt;a href="https://learn.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-analyzer"&gt;Windows Performance Analyzer&lt;/a&gt; (WPA) to see the results. I wanted &lt;em&gt;more scientific&lt;/em&gt; results than a wall clock :)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I did a run with roslyn compiler and other run with builtin compiler.&lt;/li&gt;
&lt;li&gt;I did open root Dynamics CRM page (a dashboard). I still use Legacy interface, but it doesn't prevent us making a point on csc.exe performance.&lt;/li&gt;
&lt;li&gt;Compilation resulted in generating 440 files.&lt;/li&gt;
&lt;li&gt;I did purge ASP.NET Temporary Files before each run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Legacy csc.exe
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Purge files before run
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight postscript"&gt;&lt;code&gt;&lt;span class="nf"&gt;gci&lt;/span&gt; &lt;span class="nf"&gt;-Recurse&lt;/span&gt; &lt;span class="nf"&gt;"C:\windows\Microsoft.NET\Framework64\v4.0.30319\Temporary&lt;/span&gt; &lt;span class="nf"&gt;ASP.NET&lt;/span&gt; &lt;span class="nf"&gt;Files\root\0345f868\a571ab7d"&lt;/span&gt; &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;lastwritetime&lt;/span&gt; &lt;span class="nf"&gt;-ge&lt;/span&gt; &lt;span class="nf"&gt;'2023-04-18'&lt;/span&gt; &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;PsIsContainer&lt;/span&gt; &lt;span class="nf"&gt;-eq&lt;/span&gt; &lt;span class="nf"&gt;$false&lt;/span&gt; &lt;span class="nf"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;Remove-Item&lt;/span&gt; &lt;span class="nf"&gt;-Force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Refresh page&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Look at trace with WPA - 6 times csc.exe process was called. Time since first spawn and last one ending 199sec = &lt;strong&gt;3m19sec&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4KTQFnxR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5titxugt5v0un1ij69ii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4KTQFnxR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5titxugt5v0un1ij69ii.png" alt="WPA results for legacy csc.exe" width="800" height="170"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Roslynized csc.exe
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Purge files before run&lt;/li&gt;
&lt;li&gt;Refresh Page&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;116sec = 1m56sec&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_y50ySKv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkauoc5hupv0gg9bjm0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_y50ySKv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkauoc5hupv0gg9bjm0v.png" alt="WPA results for roslyn csc.exe" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;Okay, so that was kind of total refresh for a single page that makes many calls - normally things would be somewhat cached. I feel that navigating to other pages feels faster, but still I wouldn't say that it is fast.&lt;/p&gt;

&lt;p&gt;I'll leave roslyn compiler on as any performance improvement will help - I feel that normally navigating through pages is faster. It helps against doing context switching to other tasks while waiting on compilation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Precompiling ASP.NET Framework app
&lt;/h1&gt;

&lt;p&gt;We covered dynamic compilation - a process where compilation for relevant files happens when making request and compiling appropriate files - and that request is taking the performance impact.&lt;/p&gt;

&lt;p&gt;For ASP.NET apps you develop, you can &lt;a href="https://learn.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-getting-started/deploying-web-site-projects/precompiling-your-website-cs"&gt;precompile your website&lt;/a&gt;, so that dynamic compilation never takes place and your users don't have to wait.&lt;/p&gt;

&lt;p&gt;I also have access to a ASP.NET Framework 4.8 application and I did want to improve build times there too. It usually took 34 minutes for aspnet_compiler.exe to do its precompilation job. All I had to do was to install &lt;code&gt;Microsoft.CodeDom.Providers.DotNetCompilerPlatform&lt;/code&gt; package (latest 4.1.0 version), it did the web.config changes and copied required files to bin directory - the result was immediatelly visible - instead of 34 minutes, now it did precompilation in ~13 minutes. Solid 20 minutes shaved off!&lt;/p&gt;

&lt;p&gt;As for precompilation, it wasn't immediatelly obvjous that it is using roslyn csc.exe - because I didn't see neither &lt;code&gt;VBCSCompiler.exe&lt;/code&gt; or &lt;code&gt;csc.exe&lt;/code&gt;, just &lt;code&gt;aspnet_compiler.exe&lt;/code&gt; doing its job. But the results speak of themselves.&lt;/p&gt;

&lt;p&gt;This topic was very helpful in getting up and running with roslyn-ized csc.exe compiler: &lt;a href="https://stackoverflow.com/questions/70103543/how-to-speed-up-aspnet-compiler-exe"&gt;How to speed up aspnet_compiler.exe?&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>SQL Server Query Optimizer. And how it can hang your business.</title>
      <dc:creator>Jānis Veinbergs</dc:creator>
      <pubDate>Wed, 03 Aug 2022 09:55:00 +0000</pubDate>
      <link>https://dev.to/deacdatacenter/sql-server-query-optimizer-and-how-it-can-hang-your-business-5cjb</link>
      <guid>https://dev.to/deacdatacenter/sql-server-query-optimizer-and-how-it-can-hang-your-business-5cjb</guid>
      <description>&lt;p&gt;Dynamics AX works well until it comes to a grinding halt. Without any software or hardware configuration change. How come?&lt;/p&gt;

&lt;p&gt;Enter SQL Server Query Optimizer. And how to tame it.&lt;/p&gt;

&lt;p&gt;So occasionally some part of functionality looses performance (i.e you can never finish your operation, it just hangs). Sometimes it impacts so many areas that various real life operations are severely impacted. Having experience administering this particular system meant I should look at SQL Server that causes most of performance issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the issue
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The first thing is to look at queries which take most of the time in particular timeframe. So someone complains something is going bad - I look at last 2 hours of data collected with SQL Sentry (Now &lt;a href="https://www.sentryone.com/"&gt;SentryOne&lt;/a&gt;)&lt;code&gt;*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A-ha, some very suspicious query - average execution time is ~25s. Moreover it does many physical reads (reads from disk, data not cached into RAM). This was very easy to spot, but sometimes you get bad queries with 100ms execution time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SHiO4hTq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlsitfl1piha7r6so7ml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SHiO4hTq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlsitfl1piha7r6so7ml.png" alt="Query Stats - Top SQL - identifying bad query performance" width="880" height="305"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;P1&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;P2&lt;/span&gt; &lt;span class="n"&gt;nvarchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;P3&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;list_of_224_columns&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;CUSTINVOICEJOUR&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; 
&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;DATAAREAID&lt;/span&gt;&lt;span class="o"&gt;=@&lt;/span&gt;&lt;span class="n"&gt;P1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;LEDGERVOUCHER&lt;/span&gt;&lt;span class="o"&gt;=@&lt;/span&gt;&lt;span class="n"&gt;P2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INVOICEDATE&lt;/span&gt;&lt;span class="o"&gt;=@&lt;/span&gt;&lt;span class="n"&gt;P3&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;A super simple query if you ask me, albeit pretty wide one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lets look at the query plan. On Query History page I can right click and press Open Plan. At first I see a Clustered Index Seek and nothing else - looks perfect. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DfhKK7vN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsb07ogvsj8h7p1du6is.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DfhKK7vN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fsb07ogvsj8h7p1du6is.png" alt="Query Plan - Index Seek" width="385" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Until I put my mouse over that index to see what columns it seeks over. I look at Seek Predicates (columns which utilize index) and Predicate (columns which could utilize index, but does not). And we see only 1 column &lt;code&gt;DATAAREAID&lt;/code&gt; at Seek Predicates, which is a very rough data categorization column with only a few distinct values in there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLIqyI-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfdh77hkkjf5trv6e5a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZLIqyI-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xfdh77hkkjf5trv6e5a1.png" alt="Query Plan - Index Seek Details" width="566" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So it is more like data scanning most of the table rows each time this query executes. At previous screenshot we see it did that 180 times over period of ~2hrs.&lt;br&gt;
What we ideally want to see there is many Seek Predicate columns and possibly few Predicate ones (but it depends on the query whether it is possible). So we are dealing with &lt;a href="https://docs.microsoft.com/en-us/azure/azure-sql/identify-query-performance-issues?view=azuresql#ParamSniffing"&gt;Queries that have parameter sensitive plan (PSP)&lt;/a&gt; problems.&lt;/p&gt;

&lt;p&gt;To get a sense of how indexes are used, what does clustered index mean, what is key lookup (or bookmarked lookup) look at &lt;a href="https://vladmihalcea.com/clustered-index/"&gt;Clustered Index&lt;/a&gt; article.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;So culprit is identified (diagnostics is usually most of the work). Isn't there a better index for this query to use? Let's check. Execute &lt;code&gt;sp_helpindex 'dbo.CUSTINVOICEJOUR'&lt;/code&gt; - or better yet &lt;code&gt;sp_sqlskills_helpindex 'dbo.CUSTINVOICEJOUR'&lt;/code&gt; (&lt;a href="https://www.sqlskills.com/blogs/kimberly/use-these-updates-to-sqlskills-index-procedures/"&gt;sp_sqlskills_helpindex&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MoKB58Jk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mugmg3sxcgwdzb4sjyxc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MoKB58Jk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mugmg3sxcgwdzb4sjyxc.png" alt="sp_sqlskills_helpindex result" width="880" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From query plan we saw it uses &lt;code&gt;INVOICENUMIDX&lt;/code&gt; which is clustered index and it can only use first column for seek. However there is much better index: &lt;code&gt;LEDGERVOUCHERIDX&lt;/code&gt; which has all 3 key columns that could be used for seeking: &lt;code&gt;DATAAREAID&lt;/code&gt;, &lt;code&gt;LEDGERVOUCHER&lt;/code&gt; and &lt;code&gt;INVOICEDATE&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why are we facing this issue?
&lt;/h2&gt;

&lt;p&gt;Why did SQL Server choose obviously much more worse index? My theory is as follows: The Query plan estimated (out of &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/statistics/statistics?view=sql-server-ver16"&gt;STATISTICS&lt;/a&gt;) that there will be only 1 row. Why? If you look at query plan XML, you can see on what query parameters (Actual values for &lt;code&gt;@P1&lt;/code&gt;, &lt;code&gt;@P2&lt;/code&gt;, &lt;code&gt;@P3&lt;/code&gt;) were present when SQL decided to generate &amp;amp; cache this particular query plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;                &lt;span class="nt"&gt;&amp;lt;ParameterList&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;ColumnReference&lt;/span&gt; &lt;span class="na"&gt;Column=&lt;/span&gt;&lt;span class="s"&gt;"@P3"&lt;/span&gt; &lt;span class="na"&gt;ParameterCompiledValue=&lt;/span&gt;&lt;span class="s"&gt;"'2022-07-26 00:00:00.000'"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;ColumnReference&lt;/span&gt; &lt;span class="na"&gt;Column=&lt;/span&gt;&lt;span class="s"&gt;"@P2"&lt;/span&gt; &lt;span class="na"&gt;ParameterCompiledValue=&lt;/span&gt;&lt;span class="s"&gt;"N'&amp;lt;something&amp;gt;'"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;ColumnReference&lt;/span&gt; &lt;span class="na"&gt;Column=&lt;/span&gt;&lt;span class="s"&gt;"@P1"&lt;/span&gt; &lt;span class="na"&gt;ParameterCompiledValue=&lt;/span&gt;&lt;span class="s"&gt;"N'DMT'"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/ParameterList&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@P1&lt;/code&gt; corresponds to &lt;code&gt;DATAAREAID&lt;/code&gt;. Lets look at statistics - how much rows does SQL server expect to find:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;DBCC&lt;/span&gt; &lt;span class="n"&gt;SHOW_STATISTICS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dbo.CUSTINVOICEJOUR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DATAAREAID'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Could&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;locate&lt;/span&gt; &lt;span class="k"&gt;statistics&lt;/span&gt; &lt;span class="s1"&gt;'DATAAREAID'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;system&lt;/span&gt; &lt;span class="n"&gt;catalogs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, no statistics for particular column - we have to look at index statistics then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;DBCC&lt;/span&gt; &lt;span class="n"&gt;SHOW_STATISTICS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dbo.CUSTINVOICEJOUR'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'I_062LEDGERVOUCHERIDX'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5Sza5Y2g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlp8uoqk92mjf3fpkmds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5Sza5Y2g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlp8uoqk92mjf3fpkmds.png" alt="SHOW_STATISTICS histogram result" width="736" height="105"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sorry I'm not showing you actual values, however none of the rows say &lt;code&gt;DMT&lt;/code&gt;. Basically, when SQL server updates statistics (it looks at each 5th row or whatever percentage of rows you ask SQL to look at) - and it didn't see even one where there would be DATAAREAID = DMT. Thus SQL Server estimates there is 1 row (it never estimates 0). However SQL thinks: well, if I'll get 1 row from index LEDGERVOUCHERIDX using only first key column DATAAREAID, I'll have to lookup all those selected columns within clustered index INVOICENUMIDX - why not just directly get that row from INVOICENUMIDX which also begins with DATAAREAID and spare a key lookup (as all the data will already be within the page)? (&lt;a href="https://www.brentozar.com/blitzcache/expensive-key-lookups/"&gt;Understanding Key Lookups&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;For these particular parameters SQL Server has really found the best way to return the data, no questions about that.&lt;/p&gt;

&lt;p&gt;However, the problem (or a feature) is that SQL Server caches this query plan. Suppose DATAAREAID now would be something that statistics shows us we have 352 million rows - and SQL will now get each of this row, filter out linearly INVOCIEDATE and LEDGERVOUCHER (because chosen index doesn't have data arranged in a way that index can be utilized to not read that data in the first place).&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How about &lt;strong&gt;preventing SQL server cache this particular query plan at all&lt;/strong&gt;? It is possible to do with &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/performance/plan-guides?view=sql-server-ver16"&gt;plan guides&lt;/a&gt;, but it is a bad idea: if query is set to execute multiple hundreds of times per second, then query plan compilation adds &amp;gt;10ms of time to execute. Ideally this query would execute in ~1ms time (working few years on the same system kind of tells you how long that simple query should take), so we don't want at LEAST 10x overhead to query execution. &lt;strong&gt;Bad idea&lt;/strong&gt; in this case. But could work for queries that get executed few times OR queries that by itself takes 50ms or more to run.&lt;/li&gt;
&lt;li&gt;Previously mentioned &lt;a href="https://docs.microsoft.com/en-us/azure/azure-sql/identify-query-performance-issues?view=azuresql#ParamSniffing"&gt;Queries that have parameter sensitive plan (PSP)&lt;/a&gt; lists other hints we could use, but I'll go over and use my favorite, most stable option.&lt;/li&gt;
&lt;li&gt;Tell SQL Server to &lt;strong&gt;use particular index using plan guides&lt;/strong&gt;. That's what we are going to do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What are plan guides? As documentation puts it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Plan guides let you optimize the performance of queries when you cannot or do not want to directly change the text of the actual query in SQL Server. Plan guides influence the optimization of queries by attaching query hints or a fixed query plan to them. Plan guides can be useful when a small subset of queries in a database application provided by a third-party vendor are not performing as expected.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Perfect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I cannot directly change the query&lt;/li&gt;
&lt;li&gt;I can influence query optimization&lt;/li&gt;
&lt;li&gt;Subset of queries from third-party vendor query is not performing well. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating plan guide
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXEC&lt;/span&gt; &lt;span class="n"&gt;sp_create_plan_guide&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'[QNW-319-445612-custinvoicejour-perf]'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SELECT &amp;lt;list_of_224_columns&amp;gt; FROM CUSTINVOICEJOUR A WHERE ((DATAAREAID=@P1) AND ((LEDGERVOUCHER=@P2) AND (INVOICEDATE=@P3)))'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'SQL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'@P1 nvarchar(5),@P2 nvarchar(21),@P3 datetime'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;hints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'OPTION(TABLE HINT(A, INDEX(I_062LEDGERVOUCHERIDX)))'&lt;/span&gt;
&lt;span class="k"&gt;GO&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When plan guide is created, it immediately invalidates the existing plan and when such query comes in, generates new query plan taking into account plan guide hints.&lt;/p&gt;

&lt;p&gt;Now we must validate:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Whether new query plan uses this plan guide (i.e we provided correct &lt;code&gt;@stmt&lt;/code&gt; and &lt;code&gt;@params&lt;/code&gt; vars for &lt;code&gt;sp_create_plan_guide&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;What is the performance of the query - better or worse?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To validate the first point, we must get query from plan cache and find PlanGuideName attribute within XML. Here is a query that, among some statistical stuff, has uses_plan_guide column and actually provides a way to open query plane and validate that plan guide is in use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--irE4v4xw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idkapg6qfztnwfn54bi7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--irE4v4xw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idkapg6qfztnwfn54bi7.png" alt="Query plan XML - finding PlanGuideName" width="880" height="93"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;TOP&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;execution_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sql_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_plan_hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query_plan&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_plan&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;XML&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;creation_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_execution_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_logical_reads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_logical_reads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_logical_reads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_logical_reads&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_logical_writes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_logical_writes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_logical_writes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;avg_logical_reads&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_logical_reads&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execution_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;last_elapsed_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_elapsed_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;min_elapsed_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_elapsed_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;max_elapsed_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_elapsed_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;avg_elapsed_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_elapsed_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execution_count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;min_worker_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_worker_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;max_worker_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_worker_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;avg_worker_time_ms&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_worker_time&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execution_count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;force_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;CHARINDEX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;' INDEX('&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;uses_plan_guide&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="n"&gt;CHARINDEX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;' PlanGuideName='&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_plan&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;create_plan_guide&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'sp_create_plan_guide_from_handle @name = N&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;[provide_name]&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;, @plan_handle = '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;CONVERT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;', @statement_start_offset = '&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;statement_start_offset&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;NVARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;-- you can use this statement to force this query use currently cached query plan&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
   &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_exec_query_stats&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;
   &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="n"&gt;APPLY&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_exec_sql_text&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="n"&gt;sql_handle&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;qt&lt;/span&gt;
   &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="n"&gt;APPLY&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dm_exec_text_query_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plan_handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;statement_start_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;statement_end_offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;qp&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
    &lt;span class="n"&gt;qt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="s1"&gt;'%&amp;lt;list_of_224_columns&amp;gt; FROM CUSTINVOICEJOUR A WHERE ((DATAAREAID=@P1) AND ((LEDGERVOUCHER=@P2) AND (INVOICEDATE=@P3)))'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_worker_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this query actually gives answer to the second question too - &lt;code&gt;avg_elapsed_time_ms&lt;/code&gt; is of intereset. For me it reads &lt;strong&gt;0,32ms&lt;/strong&gt;. I can't ask for more! Moreover, within 41 seconds it has &lt;code&gt;execution_count&lt;/code&gt; of 515 times. &lt;strong&gt;Compare that to 249 executions in 186 minutes...&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In fact, the job completed so fast, it is no more to be seen on SQL Sentry query stats which has configured 5 seconds of total duration for it to be included. So unless it executes 15k+ times within a minute, I'm not seeing it there.&lt;/p&gt;

&lt;p&gt;By the way, if you ever delete/rename index, that doesn't mean SQL Server will error out executing that query - it will disregard the plan guide when compiling query plan. There are performance counter to monitor to know when you have stale plan guides: &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/performance-monitor/sql-server-sql-statistics-object?view=sql-server-ver16"&gt;Misguided Plan Executions/sec&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Microsoft is on it
&lt;/h2&gt;

&lt;p&gt;This is certainly a flaw of SQL Server being dumb. However since SQL Server 2017 and onwards, Microsoft is tackling performance issues with some &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/performance/intelligent-query-processing?view=sql-server-ver16"&gt;intelligent query processing&lt;/a&gt; which includes various feedback mechanisms that SQL Server keeps an eye on whether the particular query plan strategy at hand is good enough and adjusts accordingly. These look promising:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/performance/cardinality-estimation-sql-server?view=sql-server-ver16#cardinality-estimation-ce-feedback"&gt;Cardinality Estimation (CE) feedback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/performance/intelligent-query-processing-details?view=sql-server-ver16#parameter-sensitivity-plan-optimization"&gt;Parameter Sensitivity Plan Optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/azure-sql/identify-query-performance-issues?view=azuresql#parameter-sensitivity"&gt;Automatic tuning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If only the application supported latest-and-greatest SQL Server... so I haven't been able to test out those features.&lt;/p&gt;

&lt;p&gt;Anyways, I'm happy that the results are delivered and the system can continue working until the next query is to be found misbehaving...&lt;/p&gt;

&lt;p&gt;&lt;code&gt;*&lt;/code&gt; Note that for newer SQL Servers there are tools that may assist you with finding bad performing queries, like &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store?view=sql-server-ver16"&gt;Query Store&lt;/a&gt;. For more advanced DB Users &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/data-collection/data-collection?view=sql-server-ver16"&gt;Data Collection&lt;/a&gt; could be used or just querying plan cache and sorting by &lt;code&gt;qs.total_elapsed_time&lt;/code&gt; if the query plan is still there. System uses SQL Server 2008R2 that doesn't have Query Store, so using SQL Sentry is just doing the job faster.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>performance</category>
    </item>
    <item>
      <title>Achieving single command Infrastructure deployment using PowerShell DSC.</title>
      <dc:creator>Jānis Veinbergs</dc:creator>
      <pubDate>Fri, 08 Apr 2022 11:33:09 +0000</pubDate>
      <link>https://dev.to/janisveinbergs/achieving-single-command-infrastructure-deployment-using-powershell-dsc-20h5</link>
      <guid>https://dev.to/janisveinbergs/achieving-single-command-infrastructure-deployment-using-powershell-dsc-20h5</guid>
      <description>&lt;p&gt;So, previously I wrote about &lt;a href="https://dev.to/deacdatacenter/bringing-down-aspnet-deployment-interruptions-from-minutes-to-blip-of-a-time-with-azure-devops-112i"&gt;deployment process to IIS servers&lt;/a&gt;. However wouldn't it be nice to keep that IIS configuration in sync, moreover between production and staging environment?&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration deployment world.
&lt;/h2&gt;

&lt;p&gt;One way is to create the initial installation and configuration script. Nice and fast to setup additional server, a great first step. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The configuration has to be maintained and your script has to account for that (&lt;em&gt;if&lt;/em&gt; this already exists &lt;em&gt;then&lt;/em&gt; do nothing &lt;em&gt;else&lt;/em&gt; configure).&lt;/li&gt;
&lt;li&gt;You need to have a mechanism to run the script. Scheduler?&lt;/li&gt;
&lt;li&gt;Your systems may drift if some processes or users do some configuration.&lt;/li&gt;
&lt;li&gt;You do not know the diff between the current system configuration and the script.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many tools that may be used to provide uniform server configuration, like &lt;a href="https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/hh831791(v=ws.11)"&gt;Group Policy&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/mem/configmgr/core/understand/introduction"&gt;System Center Configuration Manager/Microsoft Endpoint Configuration Manager&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/getting-started/wingettingstarted?view=dsc-1.1"&gt;PowerShell DSC&lt;/a&gt; (Desired State Configuration), Docker and various other software configuration management tools etc. All of those could be used to help achieve completely or partially uniform configuration across multiple hosts. Some of them are not free, some of them depends on Active Directory, some of them have GUI and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter PowerShell DSC
&lt;/h2&gt;

&lt;p&gt;Lets just explore PowerShell DSC, that you don't have to license and if you, like me, enjoy using PowerShell, this may be a tool for you.&lt;/p&gt;

&lt;p&gt;Things I immediately found satisfying with PowerShell DSC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's PowerShell (with a little bit of additional syntax to learn) and no additional tools to install (apart from PowerShell modules).&lt;/li&gt;
&lt;li&gt;It works over &lt;a href="https://docs.microsoft.com/en-us/windows/win32/winrm/installation-and-configuration-for-windows-remote-management"&gt;WinRM&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;It helps you define &lt;a href="https://dev.toIaC"&gt;Infrastructure-as-Code&lt;/a&gt; (&lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code"&gt;https://en.wikipedia.org/wiki/Infrastructure_as_code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;You can either make report configuration drifts, &lt;a href="https://octopus.com/blog/getting-started-with-powershell-dsc#detecting-drift"&gt;check them manually&lt;/a&gt; or &lt;a href="https://octopus.com/blog/getting-started-with-powershell-dsc#automatically-correcting-drift"&gt;make your host to automatically re-apply&lt;/a&gt; whatever was instructed in DSC script.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In an ideal world, once our server is joined to domain, I would like a one-click install for servers, be it staging or production environment. PowerShell DSC achieves quite a lot, but not necessarily a single click deployment. We will see why and how to overcome it.&lt;/p&gt;

&lt;p&gt;Why did I go out to try PS DSC?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Didn't want to write configuration documentation - let there be text that can't be outdated.&lt;/li&gt;
&lt;li&gt;IaC - wanted to have valid configuration in source control&lt;/li&gt;
&lt;li&gt;I heavily use PowerShell and this would be a "natural extension".&lt;/li&gt;
&lt;li&gt;This has come as a bonus: On deployment, check if IIS webserver configuration is as written.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Writing DSC script
&lt;/h2&gt;

&lt;p&gt;I will be using PowerShell 5.1 with &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/overview?view=dsc-1.1"&gt;DSC 1.1&lt;/a&gt;. Within &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/overview?view=dsc-2.0"&gt;v2&lt;/a&gt;, DSC has been split out from powershell package and &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/overview?view=dsc-3.0"&gt;v3&lt;/a&gt; will provide cross-platform capabilities.&lt;/p&gt;

&lt;p&gt;What I'm going to concentrate on is overcoming hurdles when writing DSC and not the actual configuration itself. You can drill down the &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/configurations/configurations?view=dsc-1.1"&gt;concepts&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/configurations/write-compile-apply-configuration?view=dsc-1.1"&gt;how-to&lt;/a&gt; documentation to learn DSC. My requirements were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I want ONE or maybe two simple commands I can use to configure IIS servers&lt;/li&gt;
&lt;li&gt;I want to be able target all or subset of servers&lt;/li&gt;
&lt;li&gt;I want to target different environments (prod, staging)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's look at a simple &lt;code&gt;dsc.ps1&lt;/code&gt; script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SimpleConfiguration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# DSC provides only simple tasks. Usually modules will be imported to do additional tasks.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Import-DscResource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ModuleName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ComputerManagementDsc'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'PendingReboot'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'TimeZone'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SystemLocale'&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;# One can evaluate expressions to get the node list E.g: $AllNodes.NodeName - AllNodes can be passed when calling function.&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Node1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Node2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"Node3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c"&gt;# Call Resource Provider E.g: WindowsFeature, File&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;WindowsFeature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WebServerRole&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Present"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Web-Server"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;SystemLocale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EnglishLocale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SystemLocale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IsSingleInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yes"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;site1Folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DestinationPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SystemDrive&lt;/span&gt;&lt;span class="s2"&gt;\inetpub\sites\site1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Directory'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Understanding resources
&lt;/h3&gt;

&lt;p&gt;What does &lt;code&gt;SystemLocale EnglishLocale { SystemLocale = "en-US" ; IsSingleInstance = "Yes" }&lt;/code&gt; mean?&lt;/p&gt;

&lt;p&gt;It means I'm calling &lt;code&gt;SystemLocale&lt;/code&gt; resource, giving it whatever friendly  name &lt;code&gt;EnglishLocale&lt;/code&gt; and passing hashtable with properties resource accepts: &lt;code&gt;SystemLocale&lt;/code&gt;, &lt;code&gt;IsSingleInstance&lt;/code&gt;. You can get available properties like that:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-DscResource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SystemLocale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Syntax&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;SystemLocale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#ResourceName&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;IsSingleInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Yes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;SystemLocale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DependsOn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]]]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PsDscRunAsCredential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSCredential&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Why can't it be a simple install script?
&lt;/h3&gt;

&lt;p&gt;It can, but in real world - without the word "simple". It's written in &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/overview/dscforengineers?view=dsc-1.1"&gt;Desired State Configuration Overview for Engineers&lt;/a&gt;. In short: you focus on WHAT to do not on HOW. DSC provides &lt;a href="https://en.wikipedia.org/wiki/Idempotence#Computer_science_examples"&gt;idempotent&lt;/a&gt; (repeatable) deployments. Along with goodies like &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/configurations/reboot-a-node?view=dsc-1.1"&gt;Continue configuration after reboot&lt;/a&gt;, &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/configurations/configdatacredentials?view=dsc-1.1"&gt;Credential encryption&lt;/a&gt; and other goodies.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why this silly syntax?
&lt;/h3&gt;

&lt;p&gt;Well, because this resource &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/resources/get-test-set?view=dsc-1.1"&gt;under the hood&lt;/a&gt; actually implements 3 functions: &lt;code&gt;Get&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, &lt;code&gt;Test&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Get&lt;/strong&gt; is a representation of state when, for example, running &lt;a href="https://docs.microsoft.com/en-us/powershell/module/psdesiredstateconfiguration/test-dscconfiguration?view=dsc-1.1"&gt;&lt;code&gt;Test-DscConfiguration -Detailed&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://docs.microsoft.com/en-us/powershell/module/psdesiredstateconfiguration/get-dscconfiguration?view=dsc-1.1"&gt;&lt;code&gt;Get-DscConfiguration&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set&lt;/strong&gt; - Applies configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test&lt;/strong&gt; - Determines whether configuration has to be applied. If not, &lt;strong&gt;Set&lt;/strong&gt; is NOT run. That is, if system is already compliant with this resource, it won't re-run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When Custom resources gets written, these functions get implemented. For example, using built-in &lt;code&gt;Script&lt;/code&gt; resource, you also have to implement all of them. Take for example a configuration, that ensures &lt;code&gt;BUILTIN\Users&lt;/code&gt; have no access to particular folder:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Script&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;site1FolderPermissions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;GetScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Path = &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SystemDrive&lt;/span&gt;&lt;span class="s2"&gt;\inetpub\sites\site1; Acl = &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;Get-NTFSAccess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SystemDrive&lt;/span&gt;&lt;span class="s2"&gt;\inetpub\sites\site1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;TestScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-NTFSAccess&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SystemDrive&lt;/span&gt;&lt;span class="s2"&gt;\inetpub\sites\site1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'BUILTIN\Users'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;measure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExpandProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;SetScript&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Disable-NTFSAccessInheritance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SystemDrive&lt;/span&gt;&lt;span class="s2"&gt;\inetpub\sites\site1"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SystemDrive&lt;/span&gt;&lt;span class="s2"&gt;\inetpub\sites\site1"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsInherited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IdentityReference&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'BUILTIN\Users'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$acl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAccessRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Set-Acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AclObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$acl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SystemDrive&lt;/span&gt;&lt;span class="s2"&gt;\inetpub\sites\site1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;DependsOn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[File]site1Folder"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Drawbacks
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Modules
&lt;/h3&gt;

&lt;p&gt;Drawbacks or pain-points or maybe stuff that hasn't been provided by DSC out-of-the-box: Distributing modules. And this is the reason I can't have a single command to bootstrap my servers. The modules have to be installed on the host that are building the configuration AND the host that gets configuration deployed. Otherwise, when encountering &lt;code&gt;Import-DscResource -Module CertificateDsc -ModuleVersion 5.1.0&lt;/code&gt;, it will tell you:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Could not find the module '&amp;lt;CertificateDsc, 5.1.0&amp;gt;'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WSR-wID0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/8WLpIpR.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WSR-wID0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/8WLpIpR.png" alt="Could not find the module error" width="880" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However moving &lt;code&gt;Import-DscResource&lt;/code&gt; command just will result in error:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Import-DscResource cannot be specified inside of Node context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qIGMcQzT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/Q0OKrxO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qIGMcQzT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/Q0OKrxO.png" alt="Import-DscResource cannot be specified inside of Node context" width="880" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the takeaway: &lt;strong&gt;we can't install a module and use its resources in a single configuration&lt;/strong&gt;. These modules are required on the host that are building configuration and on target nodes. Modules are essential to any configuration, except the most &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/resources/resources?view=dsc-1.1#windows-built-in-resources"&gt;trivial ones&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Moreover, the &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/reference/resources/packagemanagement/packagemanagementdscresource?view=dsc-1.1"&gt;&lt;code&gt;PackageManagement&lt;/code&gt;&lt;/a&gt; resource you see in a screenshot is a module itself that must be installed from PowerShell gallery before I can actually use it to install other modules 🤦‍♂️ &lt;/p&gt;

&lt;p&gt;Remember me telling how DSC enables writing more simple configuration comparing to install scripts? Turns out with DSC comes complexity anyways if I want automatic module provisioning. 🤷‍♂️ I don't want anyone (and my future self) running the deployment having to think about what modules must be installed on source &amp;amp; target machines.&lt;/p&gt;

&lt;p&gt;So I will be using another configuration to install modules, but without any dependencies on modules. I'll run this configuration on hosts that require these modules and only then apply main configuration. First, I apply &lt;code&gt;InstallPSModules&lt;/code&gt; configuration and then the &lt;code&gt;SampleConfig&lt;/code&gt; configuration. There is a gotcha: I need modules on localhost too. But running &lt;code&gt;Start-DscConfiguration .\InstallPSModules\ -ComputerName localhost&lt;/code&gt; gives me error:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WinRM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WS-Management&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;notified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exceeded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configured&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MaxEnvelopeSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;quota&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It may be misleading. If I pass &lt;code&gt;-Credential&lt;/code&gt; parameter, specifying which user I want to use to connect to localhost (admin), then it works. This is due to the way users/computers/winrm are configured in my environment.&lt;/p&gt;

&lt;p&gt;The other thing is that it may override some other DSC configuration applied to &lt;em&gt;this&lt;/em&gt; computer. Anyways, I had to choose a tradeoff. I have a build task (a function) that installs modules on local computer and another for deploying modules to target nodes. But then I have to remember adding any dependencies in 2 places. Maybe I'm trying too hard for striving to reach the goal of "&lt;em&gt;1 simple command to perform deployment&lt;/em&gt;"...&lt;/p&gt;

&lt;p&gt;What I'm doing is installing from PowerShell gallery. You could set-up so that you have modules available on some share/local machine and copy them to target machine &lt;code&gt;C:\Windows\system32\WindowsPowerShell\v1.0\Modules&lt;/code&gt; folder. Or perhaps any of these folders:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;PS C:\Repos\psdsctest&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;C:&lt;span class="se"&gt;\T&lt;/span&gt;ools&lt;span class="se"&gt;\P&lt;/span&gt;STools&lt;span class="se"&gt;\P&lt;/span&gt;sExec64.exe &lt;span class="nt"&gt;-s&lt;/span&gt; powershell.exe &lt;span class="nt"&gt;-Command&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nv"&gt;$env&lt;/span&gt;:PSModulePath &lt;span class="nt"&gt;-split&lt;/span&gt; &lt;span class="s1"&gt;';'&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;
C:\Windows\system32\config\systemprofile\Documents\WindowsPowerShell\Modules
C:\Program Files\WindowsPowerShell\Modules
C:\Windows\system32\WindowsPowerShell\v1.0\Modules
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's &lt;code&gt;PSModulePath&lt;/code&gt; for SYSTEM account. The configuration itself is being applied in the context of &lt;code&gt;SYSTEM&lt;/code&gt; account on target nodes. &lt;strong&gt;Important&lt;/strong&gt; to keep in mind when writing configurations. If you need to apply some stuff within context of another user, read about &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/configurations/configdatacredentials?view=dsc-1.1"&gt;Credential Options in Configuration Data&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is an example build task for some configuration I wrote that copies files to particular computers before doing deployment (err, read "Hooking it all up" to see that I'm using a build tool, thus another "weird" syntax):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;copyRequiredFilesToNodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Copying &lt;/span&gt;&lt;span class="nv"&gt;$BuildRoot&lt;/span&gt;&lt;span class="s2"&gt;\winlogbeat to target nodes"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$nodeSessions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-PSSession&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComputerName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Nodes&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Invoke-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$nodeSessions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Windows\Temp\winlogbeat"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$nodeSessions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Copy-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BuildRoot&lt;/span&gt;&lt;span class="s2"&gt;\winlogbeat"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Destination&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Windows\Temp\"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ToSession&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  DSC Engine configuration for enabling reboot
&lt;/h3&gt;

&lt;p&gt;Moreover, DSC Engine itself must be configured to, for example, allow reboots with &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/configurations/reboot-a-node?view=dsc-1.1&amp;amp;viewFallbackFrom=powershell-7.2"&gt;PendingReboot resource&lt;/a&gt;. DSC Engine configuration is applied with &lt;a href="https://docs.microsoft.com/en-us/powershell/module/psdesiredstateconfiguration/set-dsclocalconfigurationmanager?view=dsc-1.1"&gt;Set-DscLocalConfigurationManager&lt;/a&gt; command and uses different configuration:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DSCLocalConfigurationManager&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DSCConfig&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AllNodes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NodeName&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="c"&gt;#allow reboot with PendingReboot resource from ComputerManagementDsc module&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="n"&gt;RebootNodeIfNeeded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Applying configuration:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-DscLocalConfigurationManager&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\DSCConfig\&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComputerName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So, multiple gotchas here. Lets hook it all up within a single build script we can call.&lt;/p&gt;
&lt;h3&gt;
  
  
  Targeting other environments
&lt;/h3&gt;

&lt;p&gt;Configuration scripts are powershell scripts. You may choose to pass parameter or detect which env you are in any other way:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERDOMAIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ieq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prod.example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"staging"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then you can use if statements or override variables based on environment.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"production"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;#Call some resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="c"&gt;#Call other resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Hooking it all up
&lt;/h2&gt;

&lt;p&gt;So I need to take a step back and &lt;code&gt;Start-DscConfiguration&lt;/code&gt; won't be my entry point to deployment. I need a .ps1 script that installs modules. Or, bear with me, I'll be using a PowerShell build tool: &lt;a href="https://github.com/nightroman/Invoke-Build"&gt;Invoke-Build&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nightroman"&gt;
        nightroman
      &lt;/a&gt; / &lt;a href="https://github.com/nightroman/Invoke-Build"&gt;
        Invoke-Build
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Build Automation in PowerShell
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a href="https://www.nuget.org/packages/Invoke-Build" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/8eb7fb946f9ca7c91053a7292b3a62348ccd826b02ae415fe2a647c7ca4d7d2c/68747470733a2f2f6275696c6473746174732e696e666f2f6e756765742f496e766f6b652d4275696c64" alt="NuGet"&gt;&lt;/a&gt;
&lt;a href="https://www.powershellgallery.com/packages/InvokeBuild" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/7e8fd1c5752dbe9350051ad21d66636d9d63b492fe2bf78d7c540748dfdeba80/68747470733a2f2f696d672e736869656c64732e696f2f706f7765727368656c6c67616c6c6572792f64742f496e766f6b654275696c642e737667" alt="PSGallery"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://raw.githubusercontent.com/nightroman/Invoke-Build/master/ib.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L-OwNMU7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://raw.githubusercontent.com/nightroman/Invoke-Build/master/ib.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
Build Automation in PowerShell&lt;/h2&gt;
&lt;p&gt;Invoke-Build is a build and test automation tool which invokes tasks defined in
PowerShell v2.0+ scripts. It is similar to psake but arguably easier to use and
more powerful. It is complete, bug free, well covered by tests.&lt;/p&gt;
&lt;p&gt;In addition to basic task processing the engine supports&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Incremental tasks with effectively processed inputs and outputs.&lt;/li&gt;
&lt;li&gt;Persistent builds which can be resumed after interruptions.&lt;/li&gt;
&lt;li&gt;Parallel builds in separate workspaces with common stats.&lt;/li&gt;
&lt;li&gt;Batch invocation of tests composed as tasks.&lt;/li&gt;
&lt;li&gt;Ability to define new classes of tasks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Invoke-Build v3.0.1+ is cross-platform with PowerShell Core.&lt;/p&gt;
&lt;p&gt;Invoke-Build can be effectively used in VSCode and ISE.&lt;/p&gt;
&lt;p&gt;Several &lt;em&gt;PowerShell Team&lt;/em&gt; projects use Invoke-Build.&lt;/p&gt;
&lt;h2&gt;
The package&lt;/h2&gt;
&lt;p&gt;The package includes the engine, helpers, and help:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/nightroman/Invoke-BuildInvoke-Build.ps1"&gt;Invoke-Build.ps1&lt;/a&gt; - invokes build scripts, this is the build engine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nightroman/Invoke-BuildBuild-Checkpoint.ps1"&gt;Build-Checkpoint.ps1&lt;/a&gt; - invokes persistent builds using the engine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nightroman/Invoke-BuildBuild-Parallel.ps1"&gt;Build-Parallel.ps1&lt;/a&gt; - invokes parallel builds using the engine&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/nightroman/Invoke-BuildResolve-MSBuild.ps1"&gt;Resolve-MSBuild.ps1&lt;/a&gt; - finds…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nightroman/Invoke-Build"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;You may use other tools too: &lt;a href="https://github.com/psake/psake"&gt;psake&lt;/a&gt;, &lt;a href="https://www.gnu.org/software/make/"&gt;make&lt;/a&gt;, &lt;a href="https://cakebuild.net/"&gt;cake&lt;/a&gt;, &lt;a href="https://fake.build/"&gt;fake&lt;/a&gt; or any other &lt;a href="https://en.wikipedia.org/wiki/List_of_build_automation_software"&gt;*ake&lt;/a&gt; you are familiar with. I look at them as a tools that make build tasks behind simple commands and help me answer: &lt;a href="https://hippocanvas.com/posts/tools%20for%20remembering%20code%20projects.md"&gt;How did I run that code again?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, we still must install Invoke-Build? Well, conveniently, there is a &lt;a href="https://github.com/nightroman/Invoke-Build.template/blob/main/samples/Script4/Script4.build.ps1"&gt;template&lt;/a&gt; for invoke-build file that detects if Invoke-Build is installed or not and installs it and we'll use it.&lt;/p&gt;

&lt;p&gt;The result ended up using 4 scripts. However it can be used as an example/bootstrap project to speed up your implementation, if you choose to go down the same path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;build.ps1&lt;/code&gt; - Invoke-Build script. Entry point&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DSCConfig.ps1&lt;/code&gt; - DSC Engine configuration. Allows reboots&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;InstallPSModules.ps1&lt;/code&gt; - Module deployment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SampleConfig.ps1&lt;/code&gt; - Where I would put my configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, I can checkout the code and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\build.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# or&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\build.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Nodes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Credential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# By the way, whatever credential you pass to the build script is only for connecting to target node and starting DSC configuration. Remember that resources itself are applied within SYSTEM context.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I'v put up the scripts at psdsctest repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/janis-veinbergs"&gt;
        janis-veinbergs
      &lt;/a&gt; / &lt;a href="https://github.com/janis-veinbergs/psdsctest"&gt;
        psdsctest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
PowerShell DSC - Infrastructure as Code. Work-around pain points.&lt;/h1&gt;
&lt;p&gt;Show how to initiate PowerShell DSC configuration with one-single command. Includes configuration of DSC Engine and module dependency installation.&lt;/p&gt;
&lt;p&gt;Available tasks:&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;.&lt;span class="pl-k"&gt;/&lt;/span&gt;build.ps1 &lt;span class="pl-k"&gt;?&lt;/span&gt;
    Name                   Jobs                                                                 Synopsis
    &lt;span class="pl-k"&gt;----&lt;/span&gt;                   &lt;span class="pl-k"&gt;----&lt;/span&gt;                                                                 &lt;span class="pl-k"&gt;--------&lt;/span&gt;
    deployDSCConfig        {buildDSCConfig&lt;span class="pl-k"&gt;,&lt;/span&gt; {}}                                                 Apply DSC engine configuration to allow reboots. Kind of a special case: https:&lt;span class="pl-k"&gt;//&lt;/span&gt;docs.mi..&lt;span class="pl-k"&gt;.&lt;/span&gt; 
    installModules         {}                                                                   Install required powershell modules &lt;span class="pl-k"&gt;for&lt;/span&gt; current host &lt;span class="pl-k"&gt;for&lt;/span&gt; build to work.
    buildDSCConfig         {}                                                                   Generate .mof files configuring local DSC settings
    buildInstallPSModules  {}                                                                   Generate .mof files &lt;span class="pl-k"&gt;for&lt;/span&gt; PowerShell module installation &lt;span class="pl-k"&gt;for&lt;/span&gt; passed nodes (prerequisite fo...
    buildConfig            {}                                                                   Generates sample config
    build                  {installModules&lt;span class="pl-k"&gt;,&lt;/span&gt; buildDSCConfig&lt;span class="pl-k"&gt;,&lt;/span&gt; buildInstallPSModules&lt;span class="pl-k"&gt;,&lt;/span&gt; buildConfig} Build project. Build will call all those other taskkks
    deploy                 {&lt;span class="pl-k"&gt;clean&lt;/span&gt;&lt;span class="pl-k"&gt;,&lt;/span&gt; build&lt;span class="pl-k"&gt;,&lt;/span&gt; deployDSCConfig&lt;span class="pl-k"&gt;,&lt;/span&gt; deployInstallPSModules...}           Deploy will push configuration to nodes. Will call build before.
    deployInstallPSModules {buildInstallPSModules&lt;span class="pl-k"&gt;,&lt;/span&gt; {}}                                          Deploy only module installation. Will push configuration to nodes.&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/janis-veinbergs/psdsctest"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Tying in Azure DevOps
&lt;/h2&gt;

&lt;p&gt;In the end, when deploying code to IIS servers, I included a call to &lt;code&gt;Test-DscConfiguration -Detailed&lt;/code&gt; to inform me whether host configuration has drifted away.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$testdsc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Test-DscConfiguration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Detailed&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$testdsc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InDesiredState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Write-Warning&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Host configuration has drifted. Test-DscConfiguration returned False"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$testdsc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResourcesNotInDesiredState&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pc2Hy1lF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bas27q16rffua0flegit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pc2Hy1lF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bas27q16rffua0flegit.png" alt="Oh no, 2 servers have configuration drift?" width="880" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm not using any automatic corrections or not yet using DevOps to automatically apply configuration when DSC configuration changes have been committed. But it's possible to do that. However there comes additional complexity - perhaps I wouldn't like to reboot all servers at once if reboot is required. Or implement rolling deployment - apply configuration to some servers, see how they behave and then others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I really wish PowerShell DSC would have provided OOTB way of installing modules and requiring them after installation. Anyway, I'll have the "frame-work" ready for the next DSC project.&lt;/p&gt;

&lt;p&gt;Otherwise it's a nice tool for configuration deployment, if you can find the modules. But there are plenty.&lt;/p&gt;

&lt;p&gt;Moreover, I do recommend looking at &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/dsc-cicd?view=azure-devops"&gt;Building a Continuous Integration and Continuous Deployment pipeline with DSC&lt;/a&gt; article - it even shows how to run tests to validate your configuration.&lt;/p&gt;

&lt;p&gt;Looking forward to any comments on what could have been done better.&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>devops</category>
    </item>
    <item>
      <title>Bringing down ASP.NET deployment interruptions from minutes to blip of a time with Azure DevOps</title>
      <dc:creator>Jānis Veinbergs</dc:creator>
      <pubDate>Fri, 01 Apr 2022 08:07:32 +0000</pubDate>
      <link>https://dev.to/deacdatacenter/bringing-down-aspnet-deployment-interruptions-from-minutes-to-blip-of-a-time-with-azure-devops-112i</link>
      <guid>https://dev.to/deacdatacenter/bringing-down-aspnet-deployment-interruptions-from-minutes-to-blip-of-a-time-with-azure-devops-112i</guid>
      <description>&lt;p&gt;This is going to be about improving deployment process from classic file copy to something more effective, automated and low friction for developers and bringing no interruptions to users. &lt;a href="https://martinfowler.com/articles/developer-effectiveness.html" rel="noopener noreferrer"&gt;Maximizing Developer Effectiveness&lt;/a&gt; is actually competitive advantage after all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pain points
&lt;/h2&gt;

&lt;p&gt;Our customer, a national school management platform was experiencing inconvenience when deploying a fairly big classic .NET Framework application to 22 Windows IIS servers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deployments would be taken at night&lt;/li&gt;
&lt;li&gt;If a hotfix had to be issued at day with high load - it could take up to 30 minutes of complete system halt as IIS server farm compiles resources while pile of requests waits in a queue. &lt;/li&gt;
&lt;li&gt;The deployment is not atomic - that is, while files are being copied between v1 and v2, we would get v1.314 - while some files are updated, the others not.&lt;/li&gt;
&lt;li&gt;If we ever wanted a rollback - occasionally files would be locked, exacerbating already bad situation.&lt;/li&gt;
&lt;li&gt;The website itself consists of ~220MB of data across ~2500 number of files. And small file copy is SLOW.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The root cause of this is that .NET resources (.aspx pages, razor views) get &lt;a href="https://docs.microsoft.com/en-us/previous-versions/aspnet/ms178466(v=vs.100)" rel="noopener noreferrer"&gt;compiled to assemblies on first request&lt;/a&gt; whenever they change. When IIS is constantly bombarded with requests to all possible resources, it all must be compiled at the same time on every IIS server. On the other hand, when deployed at night, delay was minor as not many requests came in, and those that did, compiling was handled fairly quickly. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/previous-versions/aspnet/fxk122b4(v=vs.100)" rel="noopener noreferrer"&gt;ASP.NET Requests Queued&lt;/a&gt; is a good performance counter to monitor to identify when user browsers are just waiting for response and how quickly situation resolves after an update. A healthy value is 0 or something close to that. Here you see cumulative request queue for all IIS servers in evening, when load actually is not high. 4 minutes of interruptions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnje638ii0f3fdspbh1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpnje638ii0f3fdspbh1e.png" alt="IIS Request queue"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We thought about using robocopy to copy changed files based on timestamp, but that wouldn't work for us - as there could be multiple persons that would deploy the code. When Visual Studio Publishes the project, files between machines have different timestamps. Rsync would probably help greatly, but then again - some challenge to get that on developers windows machines and windows share server. And if different hosts build files, little differences in build tools may end up with different checksums. So we didn't go down this path.&lt;/p&gt;

&lt;p&gt;The webroot (and &lt;a href="https://docs.microsoft.com/en-us/iis/web-hosting/configuring-servers-in-the-windows-web-platform/shared-configuration_211" rel="noopener noreferrer"&gt;IIS Shared Configuration&lt;/a&gt;) is actually served from a single Windows share, which at least provided a low effort way to conveniently copy files to a single location and keep them, along with the IIS configuration, in sync.&lt;/p&gt;

&lt;p&gt;That's how we lived for years and it was clear that something had to change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment strategy
&lt;/h2&gt;

&lt;p&gt;We came to a decision we would like to stop depending on a file share. We already started using Azure DevOps for some other stuff. So we got an idea 💡&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep files on IIS local disk (Duh)&lt;/li&gt;
&lt;li&gt;Azure DevOps Agent would be the one compiling code.&lt;/li&gt;
&lt;li&gt;We would precompile views in advance. Locking wouldn't be an issue as we will copy within a new folder. 💎&lt;/li&gt;
&lt;li&gt;Orchestrate deployment to all IIS servers via &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/?view=azure-devops" rel="noopener noreferrer"&gt;Azure DevOps Release Pipelines&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run all .dll files through &lt;a href="https://docs.microsoft.com/en-us/dotnet/framework/tools/ngen-exe-native-image-generator" rel="noopener noreferrer"&gt;ngen&lt;/a&gt;. That would eliminate additional delay when assembly is first loaded. As C# code compiled is actually &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/managed-execution-process#compiling-to-msil" rel="noopener noreferrer"&gt;MSIL&lt;/a&gt; - an intermediate language that CPU doesn't understand any of that. JIT compiler is the one that translates that to bytecode for the processor architecture at hand. And it does so usually Just-In-Time or On-The-Fly. Or, in our case, &lt;em&gt;ngen&lt;/em&gt; (Native Image Generator)&lt;/li&gt;
&lt;li&gt;A deployment task would have to copy files within a new folder and then we would change IIS Physical path from where to serve site files. 💎&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So there are some gems there that are enough to solve our pain points. Now let's see some practical ways on achieving all of this!&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing build
&lt;/h2&gt;

&lt;p&gt;Build is the process of generating files we must copy onto IIS servers.&lt;/p&gt;

&lt;p&gt;Before we talk about build &amp;amp; deployment, let's get this straight: We are using &lt;a href="https://azure.microsoft.com/en-us/services/devops/" rel="noopener noreferrer"&gt;Azure DevOps online offering&lt;/a&gt;. However this could as well be &lt;a href="https://azure.microsoft.com/en-us/services/devops/server/" rel="noopener noreferrer"&gt;On-Premises installation&lt;/a&gt; or any other CI/CD platform of your choice. We are, however, using &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&amp;amp;tabs=browser#install" rel="noopener noreferrer"&gt;self-hosted agents&lt;/a&gt; that perform the build tasks.&lt;/p&gt;

&lt;p&gt;We are talking about solution, which consists of multiple .NET projects. Our main interest is deploying &lt;code&gt;....Web.Application&lt;/code&gt; and &lt;code&gt;..Web.Application.Payments&lt;/code&gt;. The others are mostly dependencies which must also be built. So, the developers used to build app within Visual Studio. When deploying web application, you had to right click on particular project and choose publish. From there, some settings could be configured like whether we want precompilation or not, whether we deploy straight to IIS or filesystem. In our case, it was deployed to filesystem and then copied to production. More about &lt;a href="https://docs.microsoft.com/en-us/visualstudio/deployment/quickstart-deploy-aspnet-web-app?view=vs-2022&amp;amp;tabs=azure" rel="noopener noreferrer"&gt;Publishing an ASP.NET web app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When writing &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started/what-is-azure-pipelines?view=azure-devops" rel="noopener noreferrer"&gt;Azure DevOps pipeline&lt;/a&gt;, I have to figure out what kind of MSBuild properties I have to use to invoke the "Publish" process. So, after searching the web, reading &lt;a href="https://docs.microsoft.com/en-us/visualstudio/ide/how-to-view-save-and-configure-build-log-files?view=vs-2022#to-change-the-amount-of-information-included-in-the-build-log" rel="noopener noreferrer"&gt;MSBuild diagnostic output logs&lt;/a&gt;, reading MSBuild &lt;code&gt;.target&lt;/code&gt; files, I'v come up with properties I need.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By the way, here is a quick tip on how to find relevant .target files by some keyword:&lt;/p&gt;


&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;gci&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;${env:ProgramFiles(x86)}&lt;/span&gt;&lt;span class="s2"&gt;\Microsoft Visual Studio\"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*.targets"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MvcBuildViews"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-SimpleMatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-List&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;C:\Program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x86&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nx"&gt;\Microsoft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Visual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Studio\2017\Professional\MSBuild\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.targets:849:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"CleanupForBuildMvcViews"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;" '&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;EnableCleanOnBuildForMvcViews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;'=='true' and '&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;MVCBuildViews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;'=='true' "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BeforeTargets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MvcBuildViews"&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;C:\Program&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x86&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nx"&gt;\Microsoft&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Visual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Studio\2019\Professional\MSBuild\Microsoft\VisualStudio\v16.0\Web\Microsoft.Web.Publishing.targets:847:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"CleanupForBuildMvcViews"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;" '&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;EnableCleanOnBuildForMvcViews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;'=='true' and '&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;MVCBuildViews&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;'=='true' "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BeforeTargets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MvcBuildViews"&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Or better yet, use &lt;a href="https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.ProjectSystemTools" rel="noopener noreferrer"&gt;Project System Tools&lt;/a&gt; extension with &lt;a href="https://msbuildlog.com/" rel="noopener noreferrer"&gt;MSBuild Binary and Structured Log Viewer&lt;/a&gt; to sneak peek into what MSBuild is doing - you won't regret it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, the relevant "Publish" command within Azure Devops build pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VSBuild@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish Web.Application&lt;/span&gt;  
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;solution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Web.Application'&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;/t:Build,GatherAllFilesToPublish&lt;/span&gt;
      &lt;span class="s"&gt;/p:PublishProfileName="$(publishProfileName)"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WebPublishMethod=FileSystem&lt;/span&gt;
      &lt;span class="s"&gt;/p:DeleteExistingFiles=true&lt;/span&gt;
      &lt;span class="s"&gt;/p:DeployOnBuild=true&lt;/span&gt;
      &lt;span class="s"&gt;/p:MvcBuildViews="${{ parameters.Precompile }}"&lt;/span&gt;
      &lt;span class="s"&gt;/p:PrecompileBeforePublish="${{ parameters.Precompile }}"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WDPMergeOption="MergeAllOutputsToASingleAssembly"&lt;/span&gt;
      &lt;span class="s"&gt;/p:SingleAssemblyName="Web.Application.Precompiled"&lt;/span&gt;
      &lt;span class="s"&gt;/p:UseMerge="true"&lt;/span&gt;
      &lt;span class="s"&gt;/p:DebugSymbols="True"&lt;/span&gt;
      &lt;span class="s"&gt;/p:EnableUpdateable="False"&lt;/span&gt;
      &lt;span class="s"&gt;/p:PublishUrl="$(Build.BinariesDirectory)\my"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WPPAllFilesInSingleFolder="$(Build.BinariesDirectory)\my"&lt;/span&gt;
      &lt;span class="s"&gt;/p:RunNpmScripts="$(runNpmScripts)"&lt;/span&gt;
      &lt;span class="s"&gt;/p:AutoParameterizationWebConfigConnectionStrings="false"&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildPlatform)'&lt;/span&gt;
    &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildConfiguration)'&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArchitecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x64&lt;/span&gt;
    &lt;span class="na"&gt;logFileVerbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;detailed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Note that specifying &lt;code&gt;publishProfileName&lt;/code&gt;, I use the actual publish profile used by Visual Studio Publish process and then override some properties by passing &lt;code&gt;/p&lt;/code&gt; msbuild arguments.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;WebPublishMethod&lt;/code&gt; ensures build files are put within &lt;code&gt;PublishUrl&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RunNpmScripts&lt;/code&gt; is our own custom build property used within &lt;code&gt;.pubxml&lt;/code&gt; to run some npm build process.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MvcBuildViews&lt;/code&gt;, &lt;code&gt;PrecompileBeforePublish&lt;/code&gt;, &lt;code&gt;SingleAssemblyName&lt;/code&gt;, &lt;code&gt;UseMerge&lt;/code&gt;, &lt;code&gt;EnableUpdateable&lt;/code&gt; all relate to precompilation. Before build, I can choose to disable precompilation if I want the build to happen much faster.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AutoParameterizationWebConfigConnectionStrings&lt;/code&gt; - required for copy/paste if publish profile contains connection string replacements. Without this, there will be a placeholder value that must be replaced afterwards. &lt;a href="https://stackoverflow.com/questions/16693500/is-autoparameterizationwebconfigconnectionstrings-option-the-only-way-to-preve" rel="noopener noreferrer"&gt;More on StackOverflow&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full build can be seen here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ASP.NET&lt;/span&gt;
&lt;span class="c1"&gt;# Build and test ASP.NET projects.&lt;/span&gt;
&lt;span class="c1"&gt;# Add steps that publish symbols, save build artifacts, deploy, and more:&lt;/span&gt;
&lt;span class="c1"&gt;# https://docs.microsoft.com/azure/devops/pipelines/apps/aspnet/build-aspnet-4&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;none&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(Build.SourceBranchName) $(Date:yyyyMMdd)$(Rev:.r)&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;win-dev-pool&lt;/span&gt;

&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Precompile&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Precompile&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;webApplicationProject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Web.Application'&lt;/span&gt;
  &lt;span class="na"&gt;solution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;solution.sln'&lt;/span&gt;
  &lt;span class="na"&gt;buildPlatform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x64'&lt;/span&gt;
  &lt;span class="na"&gt;buildConfiguration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Release'&lt;/span&gt;
  &lt;span class="na"&gt;releaseArchiveFilename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;webapp.7z'&lt;/span&gt;
  &lt;span class="na"&gt;releasePaymentsArchiveFilename&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payments.7z'&lt;/span&gt;
  &lt;span class="c1"&gt;# which .pubxml file to use. Don't append .pubxml&lt;/span&gt;
  &lt;span class="na"&gt;publishProfileName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;STAGING&lt;/span&gt;
  &lt;span class="na"&gt;runNpmScripts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;artifactShareName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;\\MY-APP-AGENT2\agentpublishedfiles&lt;/span&gt;

&lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NuGetToolInstaller@1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NuGetCommand@2&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;restore'&lt;/span&gt;
    &lt;span class="na"&gt;restoreSolution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(solution)'&lt;/span&gt;
    &lt;span class="na"&gt;feedsToUse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;config'&lt;/span&gt;
    &lt;span class="na"&gt;nugetConfigPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;NuGet.Config'&lt;/span&gt;

&lt;span class="c1"&gt;# npm script prerequisites&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodeTool@0&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;versionSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;14.x'&lt;/span&gt;

&lt;span class="c1"&gt;# https://docs.microsoft.com/en-us/azure/devops/pipelines/release/caching?view=azure-devops#nodejsnpm&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cache npm&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$(Agent.OS)"&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(webApplicationProject)/package.json'&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(webApplicationProject)/node_modules'&lt;/span&gt;
    &lt;span class="na"&gt;restoreKeys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"$(Agent.OS)"'&lt;/span&gt;
    &lt;span class="na"&gt;cacheHitVar&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NPM_CACHE_RESTORED&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), variables.runNpmScripts)&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npmAuthenticate@0&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workingFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(webApplicationProject)/.npmrc'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Npm@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install'&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;install'&lt;/span&gt;
    &lt;span class="na"&gt;workingDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(webApplicationProject)/'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;and(succeeded(), variables.runNpmScripts, ne(variables.NPM_CACHE_RESTORED, 'true'))&lt;/span&gt;

&lt;span class="c1"&gt;# Workaround for AspNetPrecompile to skip scanning node_modules directory and finding .c,.cpp,.h files... https://stackoverflow.com/a/20963170/50173&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CmdLine@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Hide node_modules.&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;attrib&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;+H&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$(webApplicationProject)/node_modules'&lt;/span&gt;

&lt;span class="c1"&gt;# Specificually pass PublishProfileName as empty. Because otherwise in consequent runs, this task will run npm run-script that is specified as BeforeBuild target within publish profile.&lt;/span&gt;
&lt;span class="c1"&gt;# We already run publish actions further.&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VSBuild@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build $(solution)&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;solution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(solution)'&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildPlatform)'&lt;/span&gt;
    &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildConfiguration)'&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArchitecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x64&lt;/span&gt;
    &lt;span class="na"&gt;logFileVerbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;detailed&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;/p:PublishProfileName=""&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VSTest@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test $(solution)&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildPlatform)'&lt;/span&gt;
    &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildConfiguration)'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ne(variables.NoTests, &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Target also Build, otherwise BeforeBuild target won't execute. BeforeBuild specifies npm run-script commands. So, this project gets built twice, but project build by itself is fast.&lt;/span&gt;
&lt;span class="c1"&gt;# PublishUrl actually unused as we won't use msdeploy to deploy stuff, just simple copy.&lt;/span&gt;
&lt;span class="c1"&gt;# Precompilation decision matrix: https://docs.microsoft.com/en-us/previous-versions/aspnet/bb398860(v=vs.100)&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VSBuild@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish Web.Application&lt;/span&gt;  
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;solution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Web.Application'&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;/t:Build,GatherAllFilesToPublish&lt;/span&gt;
      &lt;span class="s"&gt;/p:PublishProfileName="$(publishProfileName)"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WebPublishMethod=FileSystem&lt;/span&gt;
      &lt;span class="s"&gt;/p:DeleteExistingFiles=true&lt;/span&gt;
      &lt;span class="s"&gt;/p:DeployOnBuild=true&lt;/span&gt;
      &lt;span class="s"&gt;/p:MvcBuildViews="${{ parameters.Precompile }}"&lt;/span&gt;
      &lt;span class="s"&gt;/p:PrecompileBeforePublish="${{ parameters.Precompile }}"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WDPMergeOption="MergeAllOutputsToASingleAssembly"&lt;/span&gt;
      &lt;span class="s"&gt;/p:SingleAssemblyName="Web.Application.Precompiled"&lt;/span&gt;
      &lt;span class="s"&gt;/p:UseMerge="true"&lt;/span&gt;
      &lt;span class="s"&gt;/p:DebugSymbols="True"&lt;/span&gt;
      &lt;span class="s"&gt;/p:EnableUpdateable="False"&lt;/span&gt;
      &lt;span class="s"&gt;/p:PublishUrl="$(Build.BinariesDirectory)\my"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WPPAllFilesInSingleFolder="$(Build.BinariesDirectory)\my"&lt;/span&gt;
      &lt;span class="s"&gt;/p:RunNpmScripts="$(runNpmScripts)"&lt;/span&gt;
      &lt;span class="s"&gt;/p:AutoParameterizationWebConfigConnectionStrings="false"&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildPlatform)'&lt;/span&gt;
    &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildConfiguration)'&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArchitecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x64&lt;/span&gt;
    &lt;span class="na"&gt;logFileVerbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;detailed&lt;/span&gt;


&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;VSBuild@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish Web.Application.Payments&lt;/span&gt;  
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;solution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Web.Application.Payments'&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;/t:Build,GatherAllFilesToPublish&lt;/span&gt;
      &lt;span class="s"&gt;/p:PublishProfileName="$(publishProfileName)"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WebPublishMethod=FileSystem&lt;/span&gt;
      &lt;span class="s"&gt;/p:DeleteExistingFiles=true&lt;/span&gt;
      &lt;span class="s"&gt;/p:DeployOnBuild=true&lt;/span&gt;
      &lt;span class="s"&gt;/p:MvcBuildViews="${{ parameters.Precompile }}"&lt;/span&gt;
      &lt;span class="s"&gt;/p:PrecompileBeforePublish="${{ parameters.Precompile }}"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WDPMergeOption="MergeAllOutputsToASingleAssembly"&lt;/span&gt;
      &lt;span class="s"&gt;/p:SingleAssemblyName="Web.Application.Payments.Precompiled"&lt;/span&gt;
      &lt;span class="s"&gt;/p:UseMerge="true"&lt;/span&gt;
      &lt;span class="s"&gt;/p:DebugSymbols="True"&lt;/span&gt;
      &lt;span class="s"&gt;/p:EnableUpdateable="False"&lt;/span&gt;
      &lt;span class="s"&gt;/p:PublishUrl="$(Build.BinariesDirectory)\payments"&lt;/span&gt;
      &lt;span class="s"&gt;/p:WPPAllFilesInSingleFolder="$(Build.BinariesDirectory)\payments"&lt;/span&gt;
      &lt;span class="s"&gt;/p:AutoParameterizationWebConfigConnectionStrings="false"&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AnyCPU'&lt;/span&gt;
    &lt;span class="na"&gt;configuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(buildConfiguration)'&lt;/span&gt;
    &lt;span class="na"&gt;msbuildArchitecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;x64&lt;/span&gt;
    &lt;span class="na"&gt;logFileVerbosity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;detailed&lt;/span&gt;

&lt;span class="c1"&gt;# Artifacts&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ArchiveFiles@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Archive $(releaseArchiveFilename)&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rootFolderOrFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.BinariesDirectory)\my'&lt;/span&gt;
    &lt;span class="na"&gt;includeRootFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;archiveType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7z'&lt;/span&gt;
    &lt;span class="na"&gt;sevenZipCompression&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fastest'&lt;/span&gt;
    &lt;span class="na"&gt;archiveFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)/$(releaseArchiveFilename)'&lt;/span&gt;
    &lt;span class="na"&gt;replaceExistingArchive&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ArchiveFiles@2&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Archive $(releasePaymentsArchiveFilename)&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rootFolderOrFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.BinariesDirectory)\payments'&lt;/span&gt;
    &lt;span class="na"&gt;includeRootFolder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;archiveType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7z'&lt;/span&gt;
    &lt;span class="na"&gt;sevenZipCompression&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fastest'&lt;/span&gt;
    &lt;span class="na"&gt;archiveFile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)/$(releasePaymentsArchiveFilename)'&lt;/span&gt;
    &lt;span class="na"&gt;replaceExistingArchive&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishBuildArtifacts@1&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish website deployment Artifacts&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;PathtoPublish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(Build.ArtifactStagingDirectory)'&lt;/span&gt;
    &lt;span class="na"&gt;ArtifactName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;drop'&lt;/span&gt;
    &lt;span class="na"&gt;publishLocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;FilePath'&lt;/span&gt;
    &lt;span class="na"&gt;TargetPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$(artifactShareName)\my'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;What this does, is builds solution (dependency .dlls)&lt;/li&gt;
&lt;li&gt;Builds JavaScript SPA (Single Page App). Commands are buried within MSBuild project.&lt;/li&gt;
&lt;li&gt;Publishes 2 applications (Just generates files on disk)&lt;/li&gt;
&lt;li&gt;7zips those files&lt;/li&gt;
&lt;li&gt;Publish artifact (Azure Devops thingie - makes them available for release pipeline). In this case, they are copied to a share.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Implementing deployment
&lt;/h2&gt;

&lt;p&gt;Once we have the artifacts ready (archive of website files), we are ready to implement Release. The release must copy given files to some directory and instruct IIS to change base path from where it will server files. We had 2 options to choose from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make Build agent connect to IIS servers and perform the deployment on each IIS server. In this case, we have to write some script that will copy files onto each server and issue some commands to IIS. Moreover, we must control/see whether deployment is successful or not. And the build agent could actually be in another domain, with no direct access to production.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/release/deployment-groups/?view=azure-devops#set-up-agents-on-deployment-groups" rel="noopener noreferrer"&gt;Install Deployment agent&lt;/a&gt; on all target IIS servers. Every host then receives deployment job and does whatever it is instructed to do. We don't have to bother about any other remote communication channel other than Deployment Agent with Azure DevOps server over 443/TCP. Plus we get nice UI of seeing whether deployment succeeded, partially succeeded (if partially, which hosts failed) and on which step it failed. Neat. 
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8fhwnxs74sjoiv4s4in.png" alt="Deploy partially succeeded in Azure DevOps"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We went for the second option. Deployment agent is actually almost same as Build agent, just carries on deployment tasks.&lt;/p&gt;

&lt;p&gt;When creating release pipeline, you get to play with the UI which is nice. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97a6rcpzso4yesdcr2n3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F97a6rcpzso4yesdcr2n3.png" alt="Edit release pipeline in Azure DevOps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you see, I'v split some operations into multiple steps. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Root App Pool&lt;/em&gt; - ensures appropriate application pool is created on IIS. It is actually a one-time step and may have been created beforehand. 
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feb69ybf0ay4lpfcm7q46.png" alt="Ensure app pool in Azure DevOps"&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Copy my files&lt;/em&gt; - PowerShell script to extract 7z files:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Expand-7Zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ArchiveFileName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;System.DefaultWorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;\_Web.Application\drop\webapp.7z"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TargetPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="n"&gt;DeployTargetFolder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Ngen my&lt;/em&gt; - runs &lt;code&gt;ngen.exe&lt;/code&gt; on all .dll files found within deployment folder. Also using custom condition so this step which may take a little more than a minute, could be turned off: &lt;code&gt;and(succeeded(), eq(variables.Ngen, 'true'))&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-Alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ngen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;windir&lt;/span&gt;&lt;span class="nx"&gt;\Microsoft.NET\Framework64\&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ngen.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fullname&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DeployTargetFolder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Recurse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;dll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ngen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fullname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/nologo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Deploy my&lt;/em&gt; - Switchers virtual directory, issues some IIS configuration commands. When this stage is completed, IIS servers new code. 
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1arkdgfc5qbn296tgi62.png" alt="Switch IIS physical path in Azure DevOps"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The great thing is that if any of steps fail for ANY IIS server before Deploy step, deploy won't run for ANY IIS server and they will all be still consistent.&lt;/p&gt;

&lt;p&gt;In case of a &lt;strong&gt;rollback&lt;/strong&gt;, we can open appropriate release and for "Deploy my" stage, press Redeploy. IIS will immediately switch back to appropriate folder.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fow578k7ord5kugt9dhff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fow578k7ord5kugt9dhff.png" alt="Redeploy stage in Azure DevOps"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawbacks
&lt;/h2&gt;

&lt;p&gt;Using cloud hosted solution, if issues do happen, we won't be able to run our deployment pipeline. &lt;a href="https://status.dev.azure.com/_history" rel="noopener noreferrer"&gt;And they do happen&lt;/a&gt;. However, this may impact us on the rare case of rushing some kind of hotfix out. There are 2 steps we can take in this case:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change IIS path manually to previous deployment folder (rollback)&lt;/li&gt;
&lt;li&gt;Build via developer Visual Studio and copy appropriate DLL manually. Hotfix usually doesn't involve much code changes and is probably contained within a single or few files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Otherwise we just wait for while DevOps issue gets resolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  End result
&lt;/h2&gt;

&lt;p&gt;Everything is actually configured that, when &lt;code&gt;production&lt;/code&gt; branch gets new code, build is run automatically. After build, deployment is run automatically but stops for an approval. With &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/integrations/slack?view=azure-devops" rel="noopener noreferrer"&gt;Azure Pipelines Slack app&lt;/a&gt; we just get a message within channel where upon Approve button press, code goes into production.&lt;/p&gt;

&lt;p&gt;This doesn't include database updates, which still must be performed manually, but eventually &lt;a href="https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications?view=sql-server-ver15" rel="noopener noreferrer"&gt;DACPAC deployment&lt;/a&gt; can be incorporated within a pipeline too. But luckily, database updates are more rare than backend code/frontend updates.&lt;/p&gt;

&lt;p&gt;Judging from the Request Queue size, guess where did deployment happen?&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7hd4pqognk6m7lkyex8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7hd4pqognk6m7lkyex8.png" alt="Good IIS Requests queue size after deploy"&gt;&lt;/a&gt;&lt;br&gt;
Yeah, that little spike just before 09:00. Except, vertical axis shows max 30 instead of 3k and horizontally, just a blip of a time.&lt;/p&gt;

&lt;p&gt;For a fair picture I should share another deployment request queue graphic: &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54gthaq2spv79ie7zgf3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F54gthaq2spv79ie7zgf3.png" alt="IIS Request queue size after deploy with some spikes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Queue size spiked to almost 300. However it happens on some IIS servers, in this case 3 out of 10. After 60-90 seconds, queue size dropped, so fairly short period of time compared to what we experienced before. But it's not like those IIS completely stopped processing GET/POST requests - actually only minority of requests queued up. Currently I don't know the cause. Maybe if you have any thoughts on what may cause it, leave down in the comments.&lt;/p&gt;

&lt;p&gt;In the end, the results leave everyone happy!&lt;/p&gt;

&lt;p&gt;On an upcoming post, I'd like to share how these IIS server/Windows OS settings can be installed, managed and kept in sync with &lt;a href="https://docs.microsoft.com/en-us/powershell/dsc/getting-started/wingettingstarted?view=dsc-1.1" rel="noopener noreferrer"&gt;PowerShell DSC&lt;/a&gt;. And you don't have to keep a separate documentation file somewhere that may drift and be outdated in time. Stay tuned! 👋&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>devops</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
