<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Coding from the field</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/fieldcoding/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/fieldcoding/feed.xml</link>
    <description>Info series to share interesting findings or projects regarding platforms / infrastructure coding.</description>
    <lastBuildDate>Sat, 06 Apr 2019 10:49:01 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Resetting WMI repository - dos and dont's</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/resetting-wmi-repository-dos-and-donts</link>
      <pubDate>Wed, 10 Oct 2018 11:32:14 GMT</pubDate>
      <dc:creator><![CDATA[Michael Frommhold MSFT]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=1005</guid>
      <description><![CDATA[Today's topic:
Resetting WMI Repository
dos and dont's
Some people like Windows Management...]]></description>
      <content:encoded><![CDATA[Today's topic:
<p align="center"><b><span style="color: #4f6228"><span>Resetting WMI Repository</span></span></b></p>
<p align="center"><b><span style="color: #4f6228">dos and dont's</span></b></p>
<span>Some people like Windows Management Instrumentation (WMI) because of its power - some just hate it. The ones who hate it often claim that the WMI repository is not really reliable and often gets corrupt / unusable. </span>

Even though this may happen - it's not a usual scenario. Anyhow - if you face a corrupt WMI repository - you need to repair it.
The basic steps would be
<ul>
 	<li>set WinMgmt service state to <em>Disabled</em></li>
 	<li>stop WinMgmt service</li>
 	<li>walk through all dlls in %windir%\system32\wbem and re-register those</li>
 	<li>call /<em>regserver</em> method on WMI private server executable (wmiprvse.exe)</li>
 	<li>call <em>/resetrepository</em> method on WinMgmt service executable to  reset the WMI repository to the initial state and restore MOF files that contain the <em>#pragma autorecover</em> statement</li>
 	<li>set WinMgmt service state to <em>Auto</em></li>
 	<li>start WinMgmt service</li>
 	<li>walk through all <span style="text-decoration: underline">relevant</span> Windows Management Object Files (.mof) in %windir%\system32\wbem and all Windows Management Language Files (.mfl) in language subfolders of %windir%\system32\wbem and re-register them by mofcomp.exe calls.</li>
 	<li>restart machine</li>
</ul>
There are various articles out there that describe how to do it - some even provide automation code. Unfortunately most of these disregard the fact that the purpose of some MOF files is to uninstall the WMI namespace / class defined in the MOF - they just run through all MOF files and register those.
Saying - if you mofcomp all MOF files found in the wbem folder - you may end up with an even more unusable WMI repository - just because you uninstalled necessary WMI data ...

How you detect MOFs / MFLs that are intended to uninstall WMI namespaces / classes:

Some MOFs / MFLs are very kind to the admins and have 'uninstall' or 'remove' as part of their name - but this does not detect all classes that uninstall WMI data. To reliably identify the MOFs / MFLs to be registered by mofcomp -&gt; do the following:
<ul>
 	<li>skip MOFs / MFLs that have 'autorecover' as part of their name</li>
 	<li>skip MOFs / MFLs that have 'uninstall' or 'remove' as part of their name</li>
 	<li>skip MOFs / MFLs that contain the <em>#pragma autorecover</em> statement (already handled by winmgmt /resetrepository)</li>
 	<li>skip MOFs / MFLs that do not contain the <em>#pragma autorecover</em> statement but either <em>#pragma deleteinstance</em> or <em>#pragma deleteclass</em> statement(s)</li>
</ul>
To bring this all together in some PoSh code:

[code lang="powershell"]
function DisableService([System.ServiceProcess.ServiceController]$svc)
{ Set-Service -Name $svc.Name -StartupType Disabled }

function EnableServiceAuto([System.ServiceProcess.ServiceController]$svc)
{ Set-Service -Name $svc.Name -StartupType Automatic }

function StopService([System.ServiceProcess.ServiceController]$svc)
{
   [string]$dep = ([string]::Empty)

   foreach ($depsvc in $svc.DependentServices)
   { $dep += $depsvc.DisplayName + &quot;, &quot; }

   Write-Host &quot;Stopping $($svc.DisplayName) and its dependent services ($dep)&quot;

   $svc.Stop()

   $svc.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Stopped)

   Write-Host &quot;Stopped $($svc.DisplayName)&quot;
}

function StartService([System.ServiceProcess.ServiceController]$svc, [bool]$handleDependentServices)
{
   if ($handleDependentServices)
   { Write-Host &quot;Starting $($svc.DisplayName) and its dependent services&quot; }

   else
   { Write-Host &quot;Starting $($svc.DisplayName)&quot; }

   if (!$svc.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
   {
      try
      {
         $svc.Start()

         $svc.WaitForStatus([System.ServiceProcess.ServiceControllerStatus]::Running)
      }

      catch { }
   }

   Write-Host &quot;Started $($svc.DisplayName)&quot;
   
   if ($handleDependentServices)
   {
       [System.ServiceProcess.ServiceController]$depsvc = $null;

      foreach ($depsvc in $svc.DependentServices)
      { 
        if ($depsvc.StartType -eq [System.ServiceProcess.ServiceStartMode]::Automatic)
        { StartService $depsvc $handleDependentServices }
      }
   }
} 

function RegSvr32([string]$path)
{
   Write-Host &quot;Registering $path&quot;

   regsvr32.exe $path /s
}

function RegisterMof([System.IO.FileSystemInfo]$item)
{
   [bool]$register = $true

   Write-Host &quot;Inspecting: $($item.FullName)&quot;

   if ($item.Name.ToLowerInvariant().Contains('uninstall'))
   {
      $register = $false
      Write-Host &quot;Skipping - uninstall file: $($item.FullName)&quot;
   }

   elseif ($item.Name.ToLowerInvariant().Contains('remove'))
   {
      $register = $false
      Write-Host &quot;Skipping - remove file: $($item.FullName)&quot;
   }

   else
   {
      $txt = Get-Content $item.FullName
 
      if ($txt.Contains('#pragma autorecover'))
      {
         $register = $false
         Write-Host &quot;Skipping - autorecover: $($item.FullName)&quot;
      }

      elseif ($txt.Contains('#pragma deleteinstance'))
      {
         $register = $false
         Write-Host &quot;Skipping - deleteinstance: $($item.FullName)&quot;
      }

      elseif ($txt.Contains('#pragma deleteclass'))
      {
         $register = $false
         Write-Host &quot;Skipping - deleteclass: $($item.FullName)&quot;
      }
   }

   if ($register)
   {
      Write-Host &quot;Registering $($item.FullName)&quot;
      mofcomp $item.FullName
   }
}

function HandleFSO([System.IO.FileSystemInfo]$item, [string]$targetExt)
{
   if ($item.Extension -ne [string]::Empty)
   {
      if ($targetExt -eq 'dll')
      {
         if ($item.Extension.ToLowerInvariant() -eq '.dll')
         { RegSvr32 $item.FullName }
      }

      elseif ($targetExt -eq 'mof')
      {
         if (($item.Extension.ToLowerInvariant() -eq '.mof') -or ($item.Extension.ToLowerInvariant() -eq '.mfl'))
         { RegisterMof $item }
      }
   }
}

# get Winmgmt service
[System.ServiceProcess.ServiceController]$wmisvc = Get-Service 'winmgmt'

# disable winmgmt service
DisableService $wmisvc

# stop winmgmt service
StopService $wmisvc

# get wbem folder
[string]$wbempath = [Environment]::ExpandEnvironmentVariables(&quot;%windir%\system32\wbem&quot;)

[System.IO.FileSystemInfo[]]$itemlist = Get-ChildItem $wbempath -Recurse | Where-Object { $_.FullName.Contains('AutoRecover') -ne $true}

[System.IO.FileSystemInfo]$item = $null

# walk dlls
foreach ($item in $itemlist)
{ HandleFSO $item 'dll' }

# call /regserver method on WMI private server executable
wmiprvse /regserver

# call /resetrepository method on WinMgmt service executable
winmgmt /resetrepository

# enable winmgmt service
EnableServiceAuto $wmisvc

# start winmgmt service
StartService $wmisvc $true

# walk MOF / MFLs
foreach ($item in $itemlist)
{ HandleFSO $item 'mof' }
[/code]

&nbsp;

Kudos to my dear colleague Constantin - one of the WMI haters :- )

Have fun resetting...

4/6/2019: Updated PoSh code to only start dependent services if start mode of those is set to 'Automatic'

Michael
Have keyboard. Will travel.]]></content:encoded>
    </item>
    <item>
      <title>Netlogon Debug helper</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/netlogon-debug-helper</link>
      <pubDate>Thu, 03 May 2018 10:45:00 GMT</pubDate>
      <dc:creator><![CDATA[Michael Frommhold MSFT]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=815</guid>
      <description><![CDATA[Today's topic:
Netlogon Debug helper
Sometimes it can be helpfull to get inforamtion about actions...]]></description>
      <content:encoded><![CDATA[Today's topic:
<p align="center"><b><span style="color: #4f6228">Netlogon Debug helper<span></span></span></b></p>
<span>Sometimes it can be helpfull to get inforamtion about actions performed by the Netlogon service on a machine. To gather such information you need to enable Netlogon Debug Logging. Just to make things easier - you will find download links at the end of the article for a little helper program that:
</span>
<ul>
 	<li>helps building the debug flag bit mask corresponding to your needs</li>
 	<li>explains the bits int the debug flag bit <a href="https://msdnshared.blob.core.windows.net/media/2018/05/NlDbgHlp.png"></a>mask</li>
 	<li>sets the desired value in the registry</li>
 	<li>restats Netlogon service</li>
</ul>
Some basics - to configrure Netlogn Debug Logging we need to set a DWORD value in the registry.
Path to parent registry key:
<p style="margin-left: 40px">HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters</p>
Registry value:
<p style="margin-left: 40px">DBFlag = <i>desired debug flag bit mask</i></p>
The corresponding log entries can be found in:
<p style="margin-left: 40px">%windir%\debug\netlogon.log</p>
To control log file size (default = 20 MB) we may use the follwing DWORD value in the same registry key:
<p style="margin-left: 40px">MaximumLogFileSize = <i>maximum log file size in bytes</i></p>
<span>There are many articles out there talking about this topic - but unfortunately they all miss detailed information about the valid debug flags that can be configured.
Most of the articles are suggesting to set value 0x2080ffff - which results in very verbose debug reporting -&gt; white noise.
Apart form complexity of the analysis of the huge amount of the gathered data - your log file may be overwritten very quickly -&gt; thus you may miss log entries of interest.
</span>

Therefore, I suggest to only set those bits for debug flags that will let netlogon log only the data you are interested in.

List of the debug flag bits:

[code lang="c-sharp"]
    [Flags]
    public enum DEBUG_FLAGS : uint
    {
        NL_INIT =           0x00000001, // Netlogon initialization
        NL_MISC =           0x00000002, // Misc debug (like DCLocator)
        NL_LOGON =          0x00000004, // Logon processing
        NL_SYNC =           0x00000008, // Synchronization and replication
        NL_MAILSLOT =       0x00000010, // Mailslot messages
        NL_SITE =           0x00000020, // Sites
        NL_MSA =            0x00000040, // Managed Service Account Scavenger processing
        NL_1 =              0x00000080, // Unknown
        NL_CRITICAL =       0x00000100, // Only real important errors
        NL_SESSION_SETUP =  0x00000200, // Trusted Domain maintenance
        NL_DOMAIN =         0x00000400, // Hosted Domain maintenance
        NL_2 =              0x00000800, // UnKnown
        NL_SERVER_SESS =    0x00001000, // Server session maintenance
        NL_CHANGELOG =      0x00002000, // Change Log references
        NL_DNS =            0x00004000, // DNS name registration
        // Verbose bits
        NL_VERBOSE =        0x00008000, // Enable verbose logging
        NL_WORKER =         0x00010000, // Debug worker thread
        NL_DNS_MORE =       0x00020000, // Verbose DNS name registration
        NL_PULSE_MORE =     0x00040000, // Verbose pulse processing
        NL_SESSION_MORE =   0x00080000, // Verbose session management
        NL_REPL_TIME =      0x00100000, // replication timing output
        NL_REPL_OBJ_TIME =  0x00200000, // replication objects get/set timing output
        NL_ENCRYPT =        0x00400000, // debug encrypt and decrypt across net
        NL_SYNC_MORE =      0x00800000, // additional replication dbgprint
        NL_PACK_VERBOSE =   0x01000000, // Verbose Pack/Unpack
        NL_MAILSLOT_TEXT =  0x02000000, // Verbose Mailslot messages
        NL_CHALLENGE_RES =  0x04000000, // challenge response debug
        NL_SITE_MORE =      0x08000000, // Verbose sites
        // Control bits.
        NL_INHIBIT_CANCEL = 0x10000000, // Don't cancel API calls
        NL_TIMESTAMP =      0x20000000, // TimeStamp each output line
        NL_ONECHANGE_REPL = 0x40000000, // Only replicate one change per call
        NL_BREAKPOINT =     0x80000000 // Enter debugger on startup
    }
[/code]

Example - you are interested in logon processing -&gt; set debug flag value NL_TIMESTAMP | NL_LOGON (0x20000004).
Keep in mind - only if you set bit NL_TIMESTAMP (0x20000000) you will have the time stamp for each log entry in the netlogon.log.

FYI - white noise DBFlag value 0x2080fff translates to:
<ul>
 	<li>NL_INIT</li>
 	<li>NL_MISC</li>
 	<li>NL_LOGON</li>
 	<li>NL_SYNC</li>
 	<li>NL_MAILSLOT</li>
 	<li>NL_SITE</li>
 	<li>NL_MSA</li>
 	<li>NL_1</li>
 	<li>NL_CRITICAL</li>
 	<li>NL_SESSION_SETUP</li>
 	<li>NL_DOMAIN</li>
 	<li>NL_2</li>
 	<li>NL_SERVER_SESS</li>
 	<li>NL_CHANGELOG</li>
 	<li>NL_DNS</li>
 	<li>NL_VERBOSE</li>
 	<li>NL_SYNC_MORE</li>
 	<li>NL_TIMESTAMP</li>
</ul>
Netlogon Debug Helper Tool downloads:
<ul>
 	<li>Compiled sample code (compiled targeting .Net frameworks 3.5 and 4.5)   <a href="https://msdnshared.blob.core.windows.net/media/2018/05/NetlogonDebug_Compiled.zip">NetlogonDebug_Compiled</a></li>
 	<li>Download Visual Studio sample code project   <a href="https://msdnshared.blob.core.windows.net/media/2018/05/CodingFromTheField.NetlogonDebug.zip">CodingFromTheField.NetlogonDebug</a></li>
</ul>
All the best and have fun debugging.

Michael

PFE | Have keyboard. Will travel]]></content:encoded>
    </item>
    <item>
      <title>Automated Offline Domain Join using PowerShell and JEA</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/automated-offline-domain-join-using-powershell-and-jea</link>
      <pubDate>Fri, 27 Oct 2017 08:25:21 GMT</pubDate>
      <dc:creator><![CDATA[Raimund Andrée]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=775</guid>
      <description><![CDATA[Abstract
An offline domain join requires network access to a writable Domain Controller, which...]]></description>
      <content:encoded><![CDATA[<h2>Abstract</h2>
An offline domain join requires network access to a writable Domain Controller, which might not be desired in a DMZ scenario. Also, permissions to create computer accounts are required and permissions to enable password replication to the Read-Only Domain Controller.
This document describes the process of delegating and fully automating this process by means of PowerShell Restricted Endpoints. The setup of the endpoints is also part of this document.
<h2>Expected Result</h2>
After completing the steps outlined in the document, dedicated user accounts with no administrative permission in Active Directory can do an offline domain join without the need to contact a writable Domain Controller. The offline domain join can be done completely from the machine to join and no further steps are required on the Read-Only nor a writable Domain Controller. This process offers a very high level of comfort while giving the least privileges required to the users responsible for joining machines into the Active Directory.
<h2>Audience</h2>
Technical personnel responsible to managing Active Directory and security officers.
<h2>Requirements</h2>
An Active Directory running at least Windows Server 2008 R2. Clients and servers in the DMZ can only communicate to Read-Only Domain Controllers. Writable Domain Controllers cannot be reached from the DMZ.

Windows Management Framework 5.1 must be installed on the servers that will host the PowerShell Restricted Endpoints.

A user account with no additional privileges but having the right to create computer accounts and add someone to the group "Allowed RODC Password Replication Group".
<h2>Technical documentation</h2>
<h3>PowerShell Restricted Endpoints - background</h3>
A custom PowerShell Restricted Endpoints works like the default endpoints that already exist on every computer with at least WMF2. When creating new endpoints, you can assign permissions so that only selected users can access this new endpoint. Furthermore, these endpoints can be configured to run with predefined credentials. This can be either domain credentials or a local virtual account. In this scenario, both types are used.

To control what the users working with a custom endpoint are allowed to do, PowerShell works in a white-list mode. Only commands and functions that are explicitly allowed can be used within the endpoint. In this scenario only a single function is allowed.

Further documentation can be found here:
<ul>
 	<li><a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_session_configurations?view=powershell-5.1">About Session Configurations</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_session_configuration_files?view=powershell-5.1">About Session Configuration Files</a></li>
 	<li><a href="https://docs.microsoft.com/en-us/powershell/module/Microsoft.PowerShell.Core/Register-PSSessionConfiguration?view=powershell-3.0">Register-PSSessionConfiguration</a></li>
</ul>
Documentation how to further extend this concept with JEA:
<ul>
 	<li><a href="https://docs.microsoft.com/en-us/powershell/jea/overview">Just Enough Administration</a></li>
</ul>
<h3>PowerShell Restricted Endpoints in this specific scenario</h3>
The process in this scenario requires two endpoints. One proxy endpoint in the DMZ that the clients and servers to join can reach. The other endpoint is in the internal network and can only be reached from the proxy endpoint in the DMZ. The proxy endpoint in the DMZ just forwards the request to the one in the internal network.

The workflow is:
<ul>
 	<li>The client or server that should join the domain connects to the proxy endpoint in the DMZ.</li>
 	<li>The proxy endpoint just forwards the join request to the endpoint in the internal network.</li>
 	<li>The endpoint in the internal network does all the work to get the machine joined to the domain.
<ul>
 	<li>It checks if the site name is correct and the OU exists.</li>
 	<li>It creates the computer account using DJOIN.exe in the given OU. DJOIN.EXE creates a blob that must be stored on the machine to join. This blog is returned via the proxy endpoint to the machine requesting the domain join.</li>
 	<li>The machine is added to the group "Allowed RODC Password Replication Group".</li>
 	<li>If requested (PrepopulatePassword), the password can be pre-populated. This requires some special rights that are not part of this paper.</li>
</ul>
</li>
</ul>
<h2>Security Aspects</h2>
None of the accounts used has administrative access to the Active Directory. The only access delegated to the user account running the internal endpoint is:
<ul>
 	<li>Creating computer accounts in the dedicated OUs</li>
 	<li>Adding a computer or user to the group "Allowed RODC Password Replication Group".</li>
</ul>
Even if the machines hosting the endpoints are compromised, no critical credentials are effected.
<h3>The Deployment</h3>
<blockquote>Note: All the scripts referenced are in the appendix at the bottom.</blockquote>
Two machines are required to deploy the solution, one in the DMZ and one in the internal network. The endpoints should not be deployed to domain controllers for security reasons.

A service account is required for the endpoint running in the internal network.

The required steps to deploy the solution are:
<ol>
 	<li>
<h4>Service Account</h4>
The endpoint in the internal network runs with domain user credentials. The service user must have the right to create computer accounts in the particular OUs and to alter the member of the group "Allowed RODC Password Replication Group".

The following commands create the user account and add the required permissions to the computers container and a test OU. It also grants the service account permissions to change the membership of the "Allowed RODC Password Replication" group.

[code lang="PowerShell" gutter="false"]
New-ADUser -Name JoinUser -AccountPassword ('Password1' | ConvertTo-SecureString -AsPlainText -Force) -Enabled $true

dsacls &quot;CN=Allowed RODC Password Replication Group,CN=Users,DC=contoso,DC=com&quot; /G &quot;contoso\OfflineDomainJoin:WP;member;&quot;

dsacls &quot;OU=Test,DC=contoso,DC=com&quot; /G &quot;contoso\OfflineDomainJoin:GRGE;computer&quot;

dsacls &quot;CN=Computers,DC=contoso,DC=com&quot; /G &quot;contoso\OfflineDomainJoin:GRGE;computer&quot;
[/code]

</li>
 	<li>
<h4>Create the endpoint in the internal network</h4>
Run the script in section RestrictedEndpoint LAN.ps1 to deploy the endpoint. Please change the highlighted section according to your environment.

[code lang="PowerShell" gutter="false"]

Register-SupportPSSessionConfiguration -RunAsUser contoso\OfflineDomainJoin -RunAsUserPassword Password1 -AllowedPrincipals contoso\zMgmtRodcs$ -Force

[/code]

<blockquote>Note: contoso\zMgmtRodcs$ must be replaced with the computer account of the machine in the DMZ where the proxy endpoint is running on. Please make sure that there is a $ at the end of the argument.</blockquote>
</li>
 	<li>
<h4>Create the proxy endpoint in the internal network</h4>
The script in the section RestrictedEndpoint DMZ.ps1 creates the proxy endpoint in the DMZ. This endpoint needs to know where the internal endpoint can be found. Change this line in the parameter block accordingly.

[code lang="PowerShell" gutter="false"]
[string]$Server = 'zMgmtLan.contoso.com'
[/code]

,The other line that must be changes it the function call at the very bottom. Please replace the highlighted part with the users or groups that should be able to join computers to the domain. If you have more than one principal, provide a comma-separated list.

[code lang="PowerShell" gutter="false"]
Register-SupportPSSessionConfiguration -UseVirtualAccount -AllowedPrincipals contoso\JoinUser -Force
[/code]

After having made the changes, run the script on the machine you want to have a proxy endpoint on.</li>
 	<li>
<h4>Join a Machine Using the Automated Offline Domain Join</h4>
On the machine you want to offline-join to the domain, get the credentials for a user that is allowed to connect to the proxy endpoint.

[code lang="PowerShell" gutter="false"]
$cred = Get-Credential -Credential contoso\JoinUser
[/code]

Call the script in section OfflineDomainJoinRequest.ps1. The highlighted parts should be changed according to your environment. The parameter “Server” takes the name of the machine hosting the proxy endpoint. The given site name should match the Active Directory site the machine is part of.

[code lang="PowerShell" gutter="false"]
C:\OfflineDomainJoinRequest.ps1 -Server zMgmtRodcs.contoso.com -SiteName DMZ -OrganizationalUnit 'OU=Test,DC=contoso,DC=com' -DoNotRestart -Credential $cred 
[/code]

<blockquote>Note: The service account the internal endpoint is working with must have permissions on the given OU to create computer objects.</blockquote>
</li>
</ol>
<h2>Appendix</h2>
<ol>
 	<li>
<h3>RestrictedEndpoint LAN.ps1</h3>

[code lang="PowerShell"]
function New-ADOfflineDomainJoin
{
    param(
        [Parameter(Mandatory)]
        [string]$ComputerName,

        [string]$SiteName,

        [string]$OrganizationalUnit,

        [switch]$PrepopulatePassword
    )
    
    $domain = Get-ADDomain -Current LocalComputer
    Write-Host &quot;Current domain is '$($domain.DNSRoot)'&quot;
    $rodcs = $domain.ReadOnlyReplicaDirectoryServers
    Write-Host &quot;$($rodcs.Count) Read-Only Domain Controllers found: $($rodcs -join ', ')&quot;
    $writableDC = (Get-ADDomainController -Writable -Discover).HostName[0]
    Write-Host &quot;Writable Domain Controller is '$writableDC'&quot;

    if ($SiteName)
    {
        try
        {
            Get-ADReplicationSite -Identity $SiteName | Out-Null
        }
        catch
        {
            Write-Error &quot;The Active Directory site '$SiteName' could not be found&quot;
            return
        }
    }

    if ($OrganizationalUnit)
    {
        try
        {
            Get-ADOrganizationalUnit -Identity $OrganizationalUnit | Out-Null
        }
        catch
        {
            Write-Error &quot;The Active Directory OU '$OrganizationalUnit' could not be found&quot;
            return
        }
    }

    Write-Host
    $tempFile = [System.IO.Path]::GetTempFileName()
    Remove-Item -Path $tempFile
    Write-Host &quot;Calling DJOIN.EXE...&quot; -NoNewline

    $cmd = 'djoin.exe /provision /domain &quot;{0}&quot; /MACHINE {1} /SAVEFILE {2} /DCName {3}' -f $domain.DNSRoot, $ComputerName, $tempFile, $writableDC
    if ($OrganizationalUnit)
    {
        $cmd += &quot; /MACHINEOU $OrganizationalUnit&quot;
    }
    if ($SiteName)
    {
        $cmd += &quot; /PSITE $SiteName&quot;
    }

    Write-Host 'Running the following djoin.exe command:'
    Write-Host $cmd
    
    $djoinResult = &amp;([scriptblock]::Create($cmd))

    if ($djoinResult -like '*Computer provisioning completed successfully*')
    {
        Write-Host 'successfull'
    }
    else
    {
        Write-Host &quot;there was an error: $($djoinResult[-2])&quot;
        return
    }
    Write-Host
    
    $computer = Get-ADComputer -Identity $ComputerName -Server $writableDC
    Write-Host &quot;Adding computer account '$ComputerName' to group 'Allowed RODC Password Replication Group'&quot;
    Add-ADGroupMember -Members $computer -Identity 'Allowed RODC Password Replication Group' -Server $writableDC
    Write-Host

    if ($PrepopulatePassword)
    {
        foreach ($rodc in $rodcs)
        {
            Write-Host &quot;Prepopulating password for account '$($($computer.DistinguishedName))' to RODC '$rodc' from writable DC '$writableDC'...&quot; -NoNewline
            $repadminResult = repadmin.exe /rodcpwdrepl $rodc $writableDC &quot;&quot;$($computer.DistinguishedName)&quot;&quot;

            if ($repadminResult -like '*Successfully replicated secrets*')
            {
                Write-Host 'successfull'
            }
            else
            {
                Write-Host 'error'

                Write-Error ($repadminResult -join '. ')
                return
            }
        }
        Write-Host
    }

    Get-Content -Path $tempFile
    Remove-Item -Path $tempFile
}

function Register-SupportPSSessionConfiguration
{
    param(
        [Parameter(Mandatory, ParameterSetName = 'UserAccount')]
        [string]$RunAsUser,

        [Parameter(Mandatory, ParameterSetName = 'UserAccount')]
        [string]$RunAsUserPassword,
        
        [Parameter(Mandatory, ParameterSetName = 'VirtualAccount')]
        [switch]$UseVirtualAccount,

        [string[]]$AllowedPrincipals,
        
        [switch]$Force
    )

    $modulesToImport = 'ActiveDirectory'
    
    $path = [System.IO.Path]::GetTempFileName()
    Remove-Item -Path $path
    $path = [System.IO.Path]::ChangeExtension($path, '.pssc')
    
    $endpointName = 'OfflineDomainJoin'
    
    if ($Force -and (Get-PSSessionConfiguration -Name $endpointName -ErrorAction SilentlyContinue))
    {
        Get-PSSessionConfiguration -Name $endpointName | Unregister-PSSessionConfiguration
    }

    $param = @{}
    $param.Add('Path', $path)
    $param.Add('ModulesToImport', $modulesToImport)
    $param.Add('SessionType', 'Default')
    $param.Add('LanguageMode', 'FullLanguage')
    $param.Add('VisibleProviders', 'FileSystem')
    $param.Add('ExecutionPolicy', 'Unrestricted')
    $param.Add('Full', $true)
    
    if ($UseVirtualAccount) { $param.Add('RunAsVirtualAccount', $true) }
    $param.Add('FunctionDefinitions', @{
            Name = 'New-ADOfflineDomainJoin'
            ScriptBlock = (Get-Command -Name New-ADOfflineDomainJoin).ScriptBlock
        }
    )
    New-PSSessionConfigurationFile @param

    if ($RunAsUser)
    {
        $cred = New-Object pscredential($RunAsUser, ($RunAsUserPassword | ConvertTo-SecureString -AsPlainText -Force))
    }
    
    $param = @{
        Name = $endpointName
        Path = $path
        Force = $Force
    }
    if ($RunAsUser) { $param.Add('RunAsCredential', $cred) }
    try
    {
        Register-PSSessionConfiguration @param -ErrorAction Stop
    }
    catch
    {
        Write-Error -Exception $_.Exception
        return
    }
    finally
    {
        Remove-Item -Path $path
    }

    $pssc = Get-PSSessionConfiguration -Name $endpointName
    $psscSd = New-Object System.Security.AccessControl.CommonSecurityDescriptor($false, $false, $pssc.SecurityDescriptorSddl)

    foreach ($allowedPrincipal in $AllowedPrincipals)
    {
        $account = New-Object System.Security.Principal.NTAccount($allowedPrincipal)
        $accessType = &quot;Allow&quot;
        $accessMask = 268435456
        $inheritanceFlags = &quot;None&quot;
        $propagationFlags = &quot;None&quot;
        $psscSd.DiscretionaryAcl.AddAccess($accessType,$account.Translate([System.Security.Principal.SecurityIdentifier]),$accessMask,$inheritanceFlags,$propagationFlags)
    }

    Set-PSSessionConfiguration -Name $endpointName -SecurityDescriptorSddl $psscSd.GetSddlForm(&quot;All&quot;) -Force
} 

Register-SupportPSSessionConfiguration -RunAsUser contoso\OfflineDomainJoin -RunAsUserPassword Password1 -AllowedPrincipals contoso\zMgmtRodcs$ -Force 
[/code]

</li>
 	<li>
<h3>RestrictedEndpoint DMZ.ps1</h3>

[code lang="PowerShell"]
function Request-ADOfflineDomainJoin
{
    param(
        [Parameter(Mandatory)]
        [string]$ComputerName,

        [string]$SiteName,

        [string]$OrganizationalUnit,

        [string]$Server = 'zMgmtLan.contoso.com',

        [switch]$PrepopulatePassword
    )

    $s = New-PSSession -ComputerName $Server -ConfigurationName OfflineDomainJoin
    $blob = Invoke-Command -Session $s -ScriptBlock {

        $param = @{
            ComputerName = $using:ComputerName
        }
        if ($using:SiteName) { $param.Add('SiteName', $using:SiteName) }
        if ($using:OrganizationalUnit) { $param.Add('OrganizationalUnit', $using:OrganizationalUnit) }
        if ($using:PrepopulatePassword) { $param.Add('PrepopulatePassword', $true) }

        New-ADOfflineDomainJoin @param
    }

    $blob
}

function Register-SupportPSSessionConfiguration
{
    param(
        [Parameter(Mandatory, ParameterSetName = 'UserAccount')]
        [string]$RunAsUser,

        [Parameter(Mandatory, ParameterSetName = 'UserAccount')]
        [string]$RunAsUserPassword,
        
        [Parameter(Mandatory, ParameterSetName = 'VirtualAccount')]
        [switch]$UseVirtualAccount,

        [string[]]$AllowedPrincipals,
        
        [switch]$Force
    )

    $modulesToImport = 'ActiveDirectory'
    
    $path = [System.IO.Path]::GetTempFileName()
    Remove-Item -Path $path
    $path = [System.IO.Path]::ChangeExtension($path, '.pssc')
    
    $endpointName = 'OfflineDomainJoinProxy'
    
    if ($Force -and (Get-PSSessionConfiguration -Name $endpointName -ErrorAction SilentlyContinue))
    {
        Get-PSSessionConfiguration -Name $endpointName | Unregister-PSSessionConfiguration
    }

    $param = @{}
    $param.Add('Path', $path)
    $param.Add('ModulesToImport', $modulesToImport)
    $param.Add('SessionType', 'Default')
    $param.Add('LanguageMode', 'FullLanguage')
    $param.Add('VisibleProviders', 'FileSystem')
    $param.Add('ExecutionPolicy', 'Unrestricted')
    $param.Add('Full', $true)
    
    if ($UseVirtualAccount) { $param.Add('RunAsVirtualAccount', $true) }
    $param.Add('FunctionDefinitions', @{
            Name = 'Request-ADOfflineDomainJoin'
            ScriptBlock = (Get-Command -Name Request-ADOfflineDomainJoin).ScriptBlock
        }
    )
    New-PSSessionConfigurationFile @param

    if ($RunAsUser)
    {
        $cred = New-Object pscredential($RunAsUser, ($RunAsUserPassword | ConvertTo-SecureString -AsPlainText -Force))
    }
    
    $param = @{
        Name = $endpointName
        Path = $path
        Force = $Force
    }
    if ($RunAsUser) { $param.Add('RunAsCredential', $cred) }
    try
    {
        Register-PSSessionConfiguration @param -ErrorAction Stop
    }
    catch
    {
        Write-Error -Exception $_.Exception
        return
    }
    finally
    {
        Remove-Item -Path $path
    }

    $pssc = Get-PSSessionConfiguration -Name $endpointName
    $psscSd = New-Object System.Security.AccessControl.CommonSecurityDescriptor($false, $false, $pssc.SecurityDescriptorSddl)

    foreach ($allowedPrincipal in $AllowedPrincipals)
    {
        $account = New-Object System.Security.Principal.NTAccount($allowedPrincipal)
        $accessType = &quot;Allow&quot;
        $accessMask = 268435456
        $inheritanceFlags = &quot;None&quot;
        $propagationFlags = &quot;None&quot;
        $psscSd.DiscretionaryAcl.AddAccess($accessType,$account.Translate([System.Security.Principal.SecurityIdentifier]),$accessMask,$inheritanceFlags,$propagationFlags)
    }

    Set-PSSessionConfiguration -Name $endpointName -SecurityDescriptorSddl $psscSd.GetSddlForm(&quot;All&quot;) -Force
} 

Register-SupportPSSessionConfiguration -UseVirtualAccount -AllowedPrincipals contoso\JoinUser -Force 

[/code]

</li>
 	<li>
<h3>OfflineDomainJoinRequest.ps1</h3>

[code lang="PowerShell"]
param(
    [Parameter(Mandatory)]
    [string]$Server,

    [Parameter(Mandatory)]
    [pscredential]$Credential,

    [string]$SiteName,

    [string]$OrganizationalUnit,

    [switch]$PrepopulatePassword,

    [switch]$DoNotRestart
)

Set-Item -Path WSMan:\localhost\Client\TrustedHosts -Value $Server -Force 

$computerName = $env:COMPUTERNAME
$s = New-PSSession -ComputerName $Server -ConfigurationName OfflineDomainJoinProxy -Credential $Credential -ErrorAction Stop
$blob = Invoke-Command -Session $s -ScriptBlock {
    $param = @{
        ComputerName = $using:ComputerName
    }
   
    if ($using:SiteName) { $param.Add('SiteName', $using:SiteName) }
    if ($using:OrganizationalUnit) { $param.Add('OrganizationalUnit', $using:OrganizationalUnit) }
    if ($using:PrepopulatePassword) { $param.Add('PrepopulatePassword', $true) }

    Request-ADOfflineDomainJoin @param
}

if (-not $blob)
{
    Write-Error 'Failed to retreive blob for offline domain join'
    return
}

$tempFile = [System.IO.Path]::GetTempFileName()
$blob | Set-Content -Path $tempFile -Encoding Unicode

cmd /c DJOIN /REQUESTODJ /LOADFILE $tempFile /WINDOWSPATH %windir% /LOCALOS

Remove-Item -Path $tempFile

if (-not $DoNotRestart)
{
    Restart-Computer -Force
}
[/code]

</li>
</ol>]]></content:encoded>
    </item>
    <item>
      <title>Deploy an ADLDS lab with replication and sync in less than an hour.</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/deploy-an-adlds-lab-with-replication-and-sync-in-less-than-an-hour</link>
      <pubDate>Mon, 14 Aug 2017 22:03:51 GMT</pubDate>
      <dc:creator><![CDATA[Raimund Andrée]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=726</guid>
      <description><![CDATA[Abstract
This article helps you creating an ADLDS lab in less than an hour. If you follow the...]]></description>
      <content:encoded><![CDATA[<h2>Abstract</h2>
This article helps you creating an ADLDS lab in less than an hour. If you follow the article as it is, you will have an AD forest with an Enterprise Certificate Authority and 4 servers not joined to the domain. These 4 servers will hold an ADLDS instances that syncs some data from the AD forest. LDAPS is enabled for the ADLDS instance.
Of course, the lab can be made simpler and you can skip parts of the deployment process.
<h2>Audience</h2>
If you are an ADLDS expert and want to save time, this deployment script makes creating a lab much easier. If you are not an ADLDS expert, you can also save a lot of time and find a step by step instruction how to setup ADLDS fully automated.
<h2>Requirements</h2>
To install the lab, you need
<ul>
 	<li>A Hyper-V host running Windows 10 or Windows Server 2012 R2 or 2016 or an Azure subscription</li>
 	<li>The latest version of <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/" rel="noopener noreferrer">AutomatedLab</a></li>
</ul>
<h2>Expected Result</h2>
<ul>
 	<li>One domain controller for the contoso.com forest. The forest will have around 6.000 user accounts.</li>
 	<li>One Enterprise Certificate Authority in the contoso.com forest. The CA is used to issue certificates to the ADLDS instances to allow LDAPS communication.</li>
 	<li>4 servers that will host and replicate one ADLDS instance.</li>
 	<li>ADLDS syncs about 500 user accounts from AD.</li>
</ul>
<h2>The Deployment</h2>
<h3>VMs and basic services</h3>
To keep the deployment as simple as possible, <a href="https://github.com/AutomatedLab/AutomatedLab/" target="_blank">AutomatedLab</a> is used to deploy the virtual machines and the required services. Then some cmdlets of the <a href="https://github.com/AutomatedLab/AutomatedLab/" target="_blank">AutomatedLab</a> framework are used to deploy and configure ADLDS.
<blockquote>Note: <a href="https://github.com/AutomatedLab/AutomatedLab/" target="_blank">AutomatedLab</a> allows you to have all the code on your host computer and invoke the it on the VMs. It uses PowerShell remoting and adds more comfort to this standard feature. There is no need to care about credentials or double hope authentication issues.</blockquote>

[code lang="PowerShell"]
$labName = 'ADLDS1'

New-LabDefinition -Name $labName -DefaultVirtualizationEngine HyperV

#defining default parameter values, as these ones are the same for all the machines
$PSDefaultParameterValues = @{
    'Add-LabMachineDefinition:Memory' = 1GB
    'Add-LabMachineDefinition:OperatingSystem' = 'Windows Server 2016 SERVERDATACENTER'
    'Add-LabMachineDefinition:Network' = &quot;$labName&quot;
}

Add-LabVirtualNetworkDefinition -Name $labName -AddressSpace 192.168.40.0/24

$postInstallActivity = @()
$postInstallActivity += Get-LabPostInstallationActivity -ScriptFileName PrepareRootDomain.ps1 -DependencyFolder $labSources\PostInstallationActivities\PrepareRootDomain
$postInstallActivity += Get-LabPostInstallationActivity -ScriptFileName 'New-ADLabAccounts 2.0.ps1' -DependencyFolder $labSources\PostInstallationActivities\PrepareFirstChildDomain
Add-LabMachineDefinition -Name aDC1 -Roles RootDC -DomainName contoso.com -PostInstallationActivity $postInstallActivity

Add-LabMachineDefinition -Name aCA1 -Roles CaRoot -DomainName contoso.com

Add-LabMachineDefinition -Name aLDS01
Add-LabMachineDefinition -Name aLDS02
Add-LabMachineDefinition -Name aLDS03
Add-LabMachineDefinition -Name aLDS04

Install-Lab

$machines = Get-LabVM
Install-LabWindowsFeature -ComputerName $machines -FeatureName RSAT-AD-Tools -AsJob
Install-LabSoftwarePackage -ComputerName $machines -Path $labSources\SoftwarePackages\Notepad++.exe -CommandLine /S -AsJob
Install-LabSoftwarePackage -ComputerName $machines -Path $labSources\SoftwarePackages\winrar.exe -CommandLine /S -AsJob
Get-Job -Name 'Installation of*' | Wait-Job | Out-Null

$machines = Get-LabVM | Where-Object Name -Like *lds*
Install-LabWindowsFeature -ComputerName $machines -FeatureName ADLDS

Checkpoint-LabVM -All -SnapshotName 1

Show-LabDeploymentSummary -Detailed
[/code]

After having run this code block, all the machines, the AD domain and enterprise CA are ready. The ADLDS feature is installed on all future ADLDS servers and a snapshot was created of the whole lab. The cmdlet Show-LabDeploymentSummary prints out a detailed summary about what has been deployed.
<h3>ADLDS Definitions</h3>
The next couple of lines are definitions that will be used later. You can modify names and ports if required.

[code lang="PowerShell"]
$allAdldsServers = Get-LabVM | Where-Object Name -Like *lds*
$firstAdldsServer = $allAdldsServers | Select-Object -First 1
$otherAdldsServer = $allAdldsServers | Select-Object -Skip 1
$instanceName = 'LDS1'
$instancePartitionName = &quot;dc=$instanceName,dc=com&quot;
$instanceLdapPort = 5000
$instanceLdapsPort = 5001
$adamServiceUserName = 'AdldsService'
$adamServiceUserPassword = 'Password1'
$adamSyncUserName = 'AdamSync'
$adamSyncUserPassword = 'Password1'
$domainName = (Get-LabVM -Role RootDC).DomainName
$domainDefaultNamingContext = Invoke-LabCommand -ComputerName (Get-LabVM -Role RootDC) -ScriptBlock { (Get-ADRootDSE).DefaultNamingContext } -PassThru
$netBiosDomainName = $domainName.Split('.')[0]
$sslCertificateSubject = &quot;*.$domainName&quot; 
[/code]

<h3>Requirements like DNS and Service Accounts</h3>
Part of the lab is also enabling LDAPS for the ADLDS instance. This requires accessing the ADLDS instance by the same DNS name used to issue the LDAPS certificate. This step creates the DNS entries on the domain controller so you can reach the ADLDS instance by alds01.contoso.com to alds04.contoso.com.

[code lang="PowerShell"]
Invoke-LabCommand -ActivityName 'Add DNS record for ADLDS Server' -ComputerName (Get-LabVM -Role RootDC) -ScriptBlock {
    $zone = Get-DnsServerZone -Name $env:USERDNSDOMAIN
    
    foreach ($adldsServer in $allAdldsServers)
    {
        Add-DnsServerResourceRecordA -Name $adldsServer.Name -IPv4Address $adldsServer.IpV4Address -ZoneName $zone.ZoneName
    }
} -Variable (Get-Variable allAdldsServers)
[/code]

ADLDS will be configured to use a local service account. This service account must be created on all computers that will host the ADLDS instance. Username and password are used as defined previously.
The service account does not have to be a local administrator. However, if the service user is not an administrator and want to use LDAPS, you have to assign read permissions to the service account on the certificate's private key stored in C:\Users\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys.

[code lang="PowerShell"]
Invoke-LabCommand -ActivityName 'Create Service User' -ComputerName $allAdldsServers -ScriptBlock {
    $password = $adamServiceUserPassword | ConvertTo-SecureString -AsPlainText -Force
    $user = New-LocalUser -Name $adamServiceUserName -Password $password

    Add-LocalGroupMember -Group Administrators -Member $user
} -Variable (Get-Variable -Name adamServiceUserName, adamServiceUserPassword)
[/code]

<h3>Installing ADLDS</h3>
The next code block installs the first ADLDS instance (InstallType=unique).
Before starting the ADLDS installation an additional schema file is added to the ADAM directory. The schema file connects the auxiliary class 'msDS-BindableObject' to the user class to allow users authenticating against the ADLDS instance using LDAP simple bind.

As the additional schema file is in the ADAM directory, it can be imported as part of the ADLDS installation.

[code lang="PowerShell"]
Invoke-LabCommand -ActivityName InstallADLDS -ComputerName $firstAdldsServer -ScriptBlock {
    $ldf = @'
dn: CN=User,CN=Schema,CN=Configuration,DC=X
changetype: Modify
add: auxiliaryClass
auxiliaryClass: msDS-BindableObject
-

dn:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-
'@ | Out-File -FilePath C:\Windows\ADAM\MS-adamschemaBindableObject.LDF

    &lt;# The following text file defines the ADLDS installation. It is later used to adaminstall.exe as a parameter input file. This process is documented in [Perform an Unattended Install of an AD LDS Instance https://technet.microsoft.com/en-us/library/cc816774(v=ws.10).aspx] #&gt;
    
    @&quot;
[ADAMInstall]
InstallType=Unique
InstanceName=$instanceName
LocalLDAPPortToListenOn=$instanceLdapPort
LocalSSLPortToListenOn=$instanceLdapsPort
NewApplicationPartitionToCreate=&quot;$instancePartitionName&quot;
DataFilesPath=C:\Program Files\Microsoft ADAM\$instanceName\data
LogFilesPath=C:\Program Files\Microsoft ADAM\$instanceName\data
ImportLDIFFiles=&quot;MS-AdamSyncMetadata.ldf&quot; &quot;MS-adamschemaw2k8.LDF&quot; &quot;MS-User.ldf&quot; &quot;MS-adamschemaBindableObject.LDF&quot;
AddPermissionsToServiceAccount=Yes
ServiceAccount=$adamServiceUserName
ServicePassword=$adamServiceUserPassword
&quot;@ | Out-File -FilePath C:\adldsInstall.txt

    C:\Windows\ADAM\adaminstall.exe /answer:C:\adldsInstall.txt

} -Variable (Get-Variable -Name instanceName, instancePartitionName ,instanceLdapPort, instanceLdapsPort, adamServiceUserName, adamServiceUserPassword) -PassThru 
[/code]

Then the replicas can be installed using also the definitions made above.

[code lang="PowerShell"]
Invoke-LabCommand -ActivityName InstallADLDS -ComputerName $otherAdldsServer -ScriptBlock {
    &lt;# The following text file defines the ADLDS installation. It is later used to adaminstall.exe as a parameter input file. This process is documented in [Perform an Unattended Install of an AD LDS Instance https://technet.microsoft.com/en-us/library/cc816774(v=ws.10).aspx] #&gt;
    @&quot;
[ADAMInstall]
InstallType=Replica
InstanceName=$instanceName
LocalLDAPPortToListenOn=$instanceLdapPort
LocalSSLPortToListenOn=$instanceLdapsPort
ApplicationPartitionsToReplicate=&quot;$instancePartitionName&quot;
SourceServer=$firstAdldsServer
SourceLDAPPort=$instanceLdapPort
DataFilesPath=C:\Program Files\Microsoft ADAM\$instanceName\data
LogFilesPath=C:\Program Files\Microsoft ADAM\$instanceName\data
AddPermissionsToServiceAccount=Yes
ServiceAccount=$adamServiceUserName
ServicePassword=$adamServiceUserPassword
&quot;@ | Out-File -FilePath C:\adldsInstall.txt

    C:\Windows\ADAM\adaminstall.exe /answer:C:\adldsInstall.txt

} -Variable (Get-Variable -Name instanceName, instancePartitionName, instanceLdapPort, instanceLdapsPort, firstAdldsServer, adamServiceUserName, adamServiceUserPassword) -PassThru 
[/code]

<h3>Setting up LDAPS</h3>
Next step is to request an SSL certificate and put onto all ADLDS servers. The certificate must be in the ADLDS service account’s personal certificate store.

[code lang="PowerShell"]
$ca = Get-LabIssuingCA
#the ADLDS VMs are not domain-joined so they cannot request certificates from the Enterprise CA. The LDAPS certificate
#is requested on the CA itself...
$cert = Request-LabCertificate -Subject &quot;CN=$sslCertificateSubject&quot; -OnlineCA $ca -TemplateName 'WebServer' -ComputerName $ca -PassThru
#and then copied to the ASLDS servers
Get-LabCertificate -ComputerName $ca -SearchString $sslCertificateSubject -FindType FindBySubjectName | 
Add-LabCertificate -ComputerName $allAdldsServers -ServiceName &quot;ADAM_$instanceName&quot; -Location CERT_SYSTEM_STORE_SERVICES
[/code]

To allow someone to connect to the ADLDS instance, a user is required. This code block creates an OU first, then creates a user account and adds this account to the Readers role. LDAP Simple Bind requires SSL, as the option 'RequireSecureSimpleBind' will be set to '1' in this code block as well.

[code lang="PowerShell"]
Invoke-LabCommand -ActivityName 'Create a user to bind to ADLDS' -ComputerName $firstAdldsServer -ScriptBlock {
    $PSDefaultParameterValues = @{
        '*-AD*:Server' = &quot;localhost:$instanceLdapPort&quot;
    }

    $path = &quot;DC=$instanceName,DC=com&quot;
    $rootDse = Get-ADRootDSE

    Set-ADObject -Identity &quot;CN=Directory Service,CN=Windows NT,CN=Services,$($rootDse.configurationNamingContext)&quot; -Remove @{ 'msDS-Other-Settings' = 'RequireSecureSimpleBind=0' }
    Set-ADObject -Identity &quot;CN=Directory Service,CN=Windows NT,CN=Services,$($rootDse.configurationNamingContext)&quot; -Add @{ 'msDS-Other-Settings' = 'RequireSecureSimpleBind=1' }

    $ou = New-ADOrganizationalUnit -Name Users -Path $path -PassThru -ProtectedFromAccidentalDeletion $false

    $pass = 'Somepass1' | ConvertTo-SecureString -AsPlainText -Force
    $user = New-ADUser -Name BindTest -AccountPassword $pass -Enabled $true -Path $ou -PassThru

    $group = Get-ADGroup -Identity &quot;CN=Readers,CN=Roles,$path&quot;
    $group | Add-ADGroupMember -Members $user

    Write-Host &quot;LDAP Simple Bind test user account is: '$($user.DistinguishedName)'&quot;
} -Variable (Get-Variable -Name instanceName) -PassThru
[/code]

Now ADLDS is deployed and ready to use. You can connect to the instance using LDAP and LDAPS, but a simple bind works only over LDAPS. Replications should work between all instances. You can get a list of all replicas and about the replication state using the following commands:

[code lang="PowerShell"]
dsmgmt &quot;partition management&quot; &quot;connection&quot; &quot;connect to server localhost:5000&quot; quit 'list nc replicas &quot;dc=lds1,dc=com&quot;' quit quit

repadmin /replsum alds01:5000
[/code]

LDP.exe is quite useful to test the connectivity, authentication and to take a look at the naming contexts and their content. You can use port 5000 to connect via LDAP or 5001 using LDAPS.

<a href="https://msdnshared.blob.core.windows.net/media/2017/08/ADLDS_LDP_Connect.jpg"><img width="275" height="148" class="alignnone size-full wp-image-727" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/ADLDS_LDP_Connect.jpg" /></a>

<span style="color: #000000;font-family: Calibri">The previously created user BindTest can be used to authenticate against the ADLDS instance using a LDAP simple bind.</span>

<a href="https://msdnshared.blob.core.windows.net/media/2017/08/ADLDS_LDP_Bind.jpg"><img width="290" height="270" class="alignnone size-full wp-image-736" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/ADLDS_LDP_Bind.jpg" /></a>

The result will be like this:

[code]
res = ldap_simple_bind_s(ld, 'CN=BindTest,OU=Users,DC=LDS1,DC=com', &lt;unavailable&gt;); // v.3
Authenticated as: 'CN=BindTest,OU=Users,DC=LDS1,DC=com'.
[/code]

If you try this over an LDAP connection instead of LDAPS, you will get the following error:

[code]
res = ldap_simple_bind_s(ld, 'CN=BindTest,OU=Users,DC=LDS1,DC=com', &lt;unavailable&gt;); // v.3
Error &lt;49&gt;: ldap_simple_bind_s() failed: Invalid Credentials
Server error: 8009030C: LdapErr: DSID-0C0903D3, comment: AcceptSecurityContext error, data 202d, v3839
Error 0x8009030C The logon attempt failed
[/code]

<h3>Setting up ADAMSync</h3>
The next section of this post enables ADAMSync. ADAMSync will be configured to sync all accounts that have the email address *.test.com.

This code block takes some 500 random users from the AD domain and changes their email address to the domain test.com. Only these accounts will be synced as defined by the sync LDAP filter in the next step.

[code lang="PowerShell"]
Invoke-LabCommand -ActivityName 'Add test email address' -ComputerName (Get-LabVM -Role RootDC) -ScriptBlock {

    $rootDse = Get-ADRootDSE
    $users = Get-ADUser -Filter * -SearchBase &quot;OU=Lab Accounts,$($rootDse.defaultNamingContext)&quot;

    1..500 | ForEach-Object {
        $u = $users | Get-Random 
        $u | Set-ADObject -Replace @{ mail = &quot;$($u.GivenName).$($u.SurName)@test.com&quot; }
    }
}
[/code]

ADAMSync needs to authenticate against the AD domain, hence a user account in AD is required. The username and password is taken from the definitions done previously.

[code lang="PowerShell"]
Invoke-LabCommand -ActivityName 'Create user for ADAMSync' -ComputerName (Get-LabVM -Role RootDC) -ScriptBlock {
    $password = $adamSyncUserPassword | ConvertTo-SecureString -AsPlainText -Force
    New-ADUser -Name $adamSyncUserName -AccountPassword $password -Enabled $true
} -Variable (Get-Variable adamSyncUserName, adamSyncUserPassword) 
[/code]

This XML document defines how the ADLDS sync should work. This definition is later stored in the partition to sync using 'adamsync.exe /install'. You can view the sync configuration stored in the property 'configurationFile' on the partition root defined in the sync ('target-dn'). This property can be viewed with ldp.exe or adsiedit.msc.
The XML document contains the source and target information, logon information and an LDAP filter to select which objects should be synced.
<blockquote>NOTE: XML does not allow the usage of all characters. This can be an issue with the field object-filter 'adamsync.exe /install' does not trigger the sync, it does only define it.</blockquote>

[code lang="PowerShell"]
Invoke-LabCommand -ComputerName $firstAdldsServer -ScriptBlock {
    @&quot;
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;doc&gt;
 &lt;configuration&gt;
  &lt;description&gt;Sync for $instanceName&lt;/description&gt;
  &lt;security-mode&gt;object&lt;/security-mode&gt;
  &lt;source-ad-name&gt;$domainName&lt;/source-ad-name&gt;
  &lt;source-ad-partition&gt;$domainDefaultNamingContext&lt;/source-ad-partition&gt;
  &lt;source-ad-account&gt;$adamSyncUserName&lt;/source-ad-account&gt;
  &lt;account-domain&gt;$netBiosDomainName&lt;/account-domain&gt;
  &lt;target-dn&gt;DC=$instanceName,DC=com&lt;/target-dn&gt;
  &lt;query&gt;
   &lt;base-dn&gt;$domainDefaultNamingContext&lt;/base-dn&gt;
   &lt;object-filter&gt;(&amp;amp;(|(objectClass=user)(objectClass=group)(objectClass=publicFolder))(mail=*@test.com))&lt;/object-filter&gt;
   &lt;attributes&gt;
    &lt;include&gt;surname&lt;/include&gt;
    &lt;include&gt;givenName&lt;/include&gt;
    &lt;include&gt;mail&lt;/include&gt;
    &lt;include&gt;displayName&lt;/include&gt;
    &lt;include&gt;name&lt;/include&gt;
   &lt;/attributes&gt;	
  &lt;/query&gt;
  &lt;schedule&gt;	
   &lt;aging&gt;
    &lt;frequency&gt;5&lt;/frequency&gt;	
    &lt;num-objects&gt;0&lt;/num-objects&gt;	
   &lt;/aging&gt;
   &lt;schtasks-cmd&gt;&lt;/schtasks-cmd&gt;	
  &lt;/schedule&gt;
 &lt;/configuration&gt;
 &lt;synchronizer-state&gt;
  &lt;dirsync-cookie&gt;&lt;/dirsync-cookie&gt;
  &lt;status&gt;&lt;/status&gt;
  &lt;authoritative-adam-instance&gt;&lt;/authoritative-adam-instance&gt;
  &lt;configuration-file-guid&gt;&lt;/configuration-file-guid&gt;
  &lt;last-sync-attempt-time&gt;&lt;/last-sync-attempt-time&gt;
  &lt;last-sync-success-time&gt;&lt;/last-sync-success-time&gt;
  &lt;last-sync-error-time&gt;&lt;/last-sync-error-time&gt;
  &lt;last-sync-error-string&gt;&lt;/last-sync-error-string&gt;
  &lt;consecutive-sync-failures&gt;&lt;/consecutive-sync-failures&gt;
  &lt;user-credentials&gt;&lt;/user-credentials&gt;
  &lt;runs-since-last-object-update&gt;&lt;/runs-since-last-object-update&gt;
  &lt;runs-since-last-full-sync&gt;&lt;/runs-since-last-full-sync&gt;
 &lt;/synchronizer-state&gt;
&lt;/doc&gt;
&quot;@ | Out-File -FilePath C:\MS-AdamSyncConf.xml

    &quot;C:\Windows\ADAM\adamsync.exe /install localhost:$instanceLdapPort C:\MS-AdamSyncConf.xml /passPrompt&quot; | Out-File -FilePath C:\InstallAdamSync.bat -Encoding ascii

    $cmd = &quot;C:\Windows\ADAM\adamsync.exe /sync localhost:$instanceLdapPort '$instancePartitionName' /log C:\ADAMSyncLog{0:yyMMdd_hhmmss}.log&quot; -f (Get-Date)
    $cmd = [scriptblock]::Create($cmd)

    $t = New-JobTrigger -RepetitionInterval (New-TimeSpan -Hours 1) -RepeatIndefinitely $true -At (Get-Date).AddMinutes(1)
    Register-ScheduledJob -Name AdldsSync -ScriptBlock $cmd -Trigger $t | Out-Null

} -Variable (Get-Variable -Name domainName, instanceName, domainDefaultNamingContext, adamSyncUserName, netBiosDomainName, instanceLdapPort) -PassThru
[/code]

<blockquote>NOTE: 'adamsync.exe /install' must be called manually, as the password prompt does not work within a script. This seems to be a shortcoming of ADAMSync.exe. The code block puts the install command into a batch file that you can call on the VM hosting the first ADLDS instance using PowerShell (do not use the ISE).</blockquote>
<a href="https://msdnshared.blob.core.windows.net/media/2017/08/ADLDS_InstallAdamSync.jpg"><img width="1024" height="186" class="alignnone size-large wp-image-746" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/ADLDS_InstallAdamSync-1024x186.jpg" /></a>

After ADAMSync is installed, you can start the sync by calling 'adamsync.exe /sync'. A scheduled task was created on the firest ADLDS instance that runs the sync every 1 hour. If you want to run it right after installing ADAMSync, you can use the following command to invoke it manually.

[code lang="PowerShell"]
Get-ScheduledJob -Name AdldsSync | Set-ScheduledJob -RunNow
[/code]

The scheduled task calling ADAMSync writes a log in C:\ that contains info about all synced objects.]]></content:encoded>
    </item>
    <item>
      <title>Visualize DSC Reporting with PowerBI</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/visualize-dsc-reporting-with-powerbi</link>
      <pubDate>Sun, 21 May 2017 17:09:22 GMT</pubDate>
      <dc:creator><![CDATA[Raimund Andrée]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=665</guid>
      <description><![CDATA[Abstract
Reporting is an essential component of DSC and one of the main advantages. Always knowing...]]></description>
      <content:encoded><![CDATA[<h2>Abstract</h2>
Reporting is an essential component of DSC and one of the main advantages. Always knowing the status about your nodes and even being able to know which settings are not compliant is a big advantage compared to classical scripting or Group Policies. However, the reporting feature is not so easy to use and there are no graphical reports available yet. Dashboards and diagrams are always good to draw people’s attention. This article describes you to achieve exactly this.

Having the graphical reports should also help explaining to non-technical people the importance of Infrastructure as Code and DSC.
<h2>Requirements</h2>
This article based on <a target="_blank" href="https://blogs.technet.microsoft.com/fieldcoding/2017/05/11/using-sql-server-2016-for-a-dsc-pull-server/" rel="noopener noreferrer">Using SQL Server 2016 for a DSC Pull Server</a>. You need to move from EDB to an MDB with linked tables. All the steps required and additional details are mentioned in this article. The script to create the SQL database referenced in this article adds some views, functions and a trigger to convert the data into proper JSON, also required.

Another requirement may be – depending on the size of the configuration – described in <a target="_blank" href="https://blogs.technet.microsoft.com/fieldcoding/2017/05/20/broken-dsc-reporting-requestentitytoolarge-and-some-lcm-internals/" rel="noopener noreferrer">Broken DSC Reporting, RequestEntityTooLarge and some LCM internals</a>.

If you want to test this out without investing too much time, you may want to take a look at <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab" rel="noopener noreferrer">AutomatedLab </a>and the script <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/master/SampleScripts/Scenarios/DSC%20Pull%20Scenario%201%20(Pull%20Configuration%2C%20SQL%20Reporting).ps1" rel="noopener noreferrer">DSC Pull Scenario 1 (Pull Configuration, SQL Reporting).ps1</a> in paticular. This script deploys a lab that has a DSC Pull Server, SQL Server 2016 and PowerBI already configured.

&nbsp;
<h2>Desired Result</h2>
The result after having implemented the reporting as described here looks like this:

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/DscDashboard1.png"><img width="1024" height="592" class="alignnone size-large wp-image-666" alt="dscdashboard1" src="https://msdnshared.blob.core.windows.net/media/2017/05/DscDashboard1-1024x592.png" /></a>

This dashboard lets you very easily see the overall status of your DSC nodes. In the current version, you get these information:
<ol>
 	<li>Node Status, count of success and failure</li>
 	<li>Count of compliant / non-compliant configuration items per node</li>
 	<li>Average duration it takes to apply the configuration</li>
 	<li>Status reports and configurations per node</li>
</ol>
The dashboard is quite extensible and can visualize almost anything that is covered with the data in the DSC database.
<h2>The heavy lifting behind the scenes</h2>
<blockquote>Note: This section is not essential to read of you just want to use the reporting solution and do not face issues.</blockquote>
The pull server stores most of the data as JSON into the database. However, the JSON data cannot be converted back from JSON so easy and seems to be serialized more than once.

This is how the data looks like

[code lang="powershell"]
Invoke-Sqlcmd -Query 'SELECT TOP 1 StatusData FROM Statusreport' -Database DSC | Select-Object -ExpandProperty StatusData
[/code]

<pre>["{\"StartDate\":\"2017-05-21T18:20:51.5710000+02:00\",\"IPV6Addresses\":[\"fe80::2c81:cc87:4f57:e1d9%2\",\"::2000:0:0:0\",\"::1\",\"::2000:0:0:0\"],\"DurationInSeconds\":\"5\",\"JobID\":\"{74703B6E-3E41-11E7-8365-0017FA00000C}\",\"CurrentChecksum\":\"30B2DD729B96A4DFCB882379448A9183C00B77EE754CCA5C84D76124AE16B151\",\"MetaData\":\"Author: Install; Name: PullClient; Version: 2.0.0; GenerationDate: 05/21/2017 18:04:02; GenerationHost: XPULL1;\",\"Status\":\"Success\",\"IPV4Addresses\":[\"192.168.88.8\",\"127.0.0.1\"],\"LCMVersion\":\"2.0\",\"NumberOfResources\":\"0\",\"Type\":\"LocalConfigurationManager\",\"HostName\":\"XDC13\",\"RebootRequested\":\"False\",\"MACAddresses\":[\"00-17-FA-00-00-0C\",\"00-00-00-00-00-00-00-E0\"],\"MetaConfiguration\":{\"AgentId\":\"1B664F0D-3E3F-11E7-8365-0017FA00000C\",\"ConfigurationDownloadManagers\":[{\"RegistrationKey\":\"\",\"ServerURL\":\"https://xpull1:8080/PSDSCPullServer.svc\",\"ResourceId\":\"[ConfigurationRepositoryWeb]PullServer\",\"ConfigurationNames\":[\"Complete_xDC13\"],\"SourceInfo\":\"C:\\\\Users\\\\Install.BMW\\\\Desktop\\\\DscScripts\\\\2.2 SetupDScClients.ps1::26::9::ConfigurationRepositoryWeb\"}],\"ActionAfterReboot\":\"ContinueConfiguration\",\"LCMCompatibleVersions\":[\"1.0\",\"2.0\"],\"LCMState\":\"Idle\",\"ResourceModuleManagers\":[],\"ReportManagers\":[{\"RegistrationKey\":\"\",\"ServerURL\":\"https://xpull1:8080/PSDSCPullServer.svc\",\"ResourceId\":\"[ReportServerWeb]ReportServer\",\"SourceInfo\":\"C:\\\\Users\\\\Install.BMW\\\\Desktop\\\\DscScripts\\\\2.2 SetupDScClients.ps1::34::9::ReportServerWeb\"}],\"StatusRetentionTimeInDays\":\"10\",\"LCMVersion\":\"2.0\",\"ConfigurationMode\":\"ApplyAndAutoCorrect\",\"RefreshFrequencyMins\":\"30\",\"RebootNodeIfNeeded\":\"True\",\"SignatureValidationPolicy\":\"NONE\",\"RefreshMode\":\"Pull\",\"DebugMode\":[\"NONE\"],\"LCMStateDetail\":\"\",\"AllowModuleOverwrite\":\"False\",\"ConfigurationModeFrequencyMins\":\"15\",\"SignatureValidations\":[]},\"Locale\":\"en-US\",\"Mode\":\"Pull\"}"]
</pre>
PowerShell cannot convert this data using ConvertFrom-Json and SQL Server cannot read it either using OPENJSON. The data needs to be converted first. The solution was mentioned first in <a target="_blank" href="https://gallery.technet.microsoft.com/scriptcenter/Get-DSC-Pull-Client-Status-94c7291b" rel="noopener noreferrer">Get DSC Pull Client Status</a> and is actually quite simple. To make the conversion happen completely transparent to the user and without manual interaction, I have created a database trigger that takes care of that.
After the conversion, the StatusData column looks like this:
<pre>{"StartDate":"2017-05-21T18:57:28.2910000+02:00","IPV6Addresses":["fe80::e466:5405:663c:6ac3%3","::2000:0:0:0","::1","::2000:0:0:0"],"DurationInSeconds":"12","JobID":"{91CB841E-3E46-11E7-8365-0017FA00000D}","CurrentChecksum":"0FA45B9AE2E7CA252D64AA40DE8AFD1F9B19E02844F57498F22C1FD700F31E90","MetaData":"Author: Install; Name: Complete; Version: 2.0.0; GenerationDate: 05/21/2017 18:03:44; GenerationHost: XPULL1;","RebootRequested":"False","Status":"Success","IPV4Addresses":["192.168.88.9","127.0.0.1"],"LCMVersion":"2.0","NumberOfResources":"1","Type":"Initial","HostName":"XDC14","ResourcesInDesiredState":[{"SourceInfo":"C:UsersInstall.BMWDesktopDscScripts1.2 NewDscConfig.ps1::61::21::File","ModuleName":"PSDesiredStateConfiguration","DurationInSeconds":"0.019","InstanceName":"TestFile","StartDate":"2017-05-21T18:57:40.6890000+02:00","ResourceName":"File","ModuleVersion":"1.1","RebootRequested":"False","ResourceId":"[File]TestFile","ConfigurationName":"Complete","InDesiredState":"True"}],"MACAddresses":["00-17-FA-00-00-0D","00-00-00-00-00-00-00-E0"],"MetaConfiguration":{"AgentId":"1B67D9E9-3E3F-11E7-8365-0017FA00000D","ConfigurationDownloadManagers":[{"RegistrationKey":"","ServerURL":"https://xpull1:8080/PSDSCPullServer.svc","ResourceId":"[ConfigurationRepositoryWeb]PullServer","ConfigurationNames":["Complete_xDC14"],"SourceInfo":"C:UsersInstall.BMWDesktopDscScripts2.2 SetupDScClients.ps1::26::9::ConfigurationRepositoryWeb"}],"ActionAfterReboot":"ContinueConfiguration","LCMCompatibleVersions":["1.0","2.0"],"LCMState":"Idle","ResourceModuleManagers":[],"ReportManagers":[{"RegistrationKey":"","ServerURL":"https://xpull1:8080/PSDSCPullServer.svc","ResourceId":"[ReportServerWeb]ReportServer","SourceInfo":"C:UsersInstall.BMWDesktopDscScripts2.2 SetupDScClients.ps1::34::9::ReportServerWeb"}],"StatusRetentionTimeInDays":"10","LCMVersion":"2.0","ConfigurationMode":"ApplyAndAutoCorrect","RefreshFrequencyMins":"30","RebootNodeIfNeeded":"True","SignatureValidationPolicy":"NONE","RefreshMode":"Pull","DebugMode":["NONE"],"LCMStateDetail":"","AllowModuleOverwrite":"False","ConfigurationModeFrequencyMins":"15","SignatureValidations":[]},"Locale":"en-US","Mode":"Pull"}
</pre>
Now the data can be just taken to PowerShell and converted with ConvertFrom-Json and also SQL Server is happy.

When the LCM starts applying a configuration (Start-DscConfiguration), it writes an entry of type ‘Initial’ into the reporting database. This record has a StartTime but not yet a valid EndTime. Also, StatusData only contains some basic information about the node.
<blockquote>Note: The OperationType ‘Initial’ indicates that the node applies a configuration the first time. All subsequent applications are logged with the OperationType ‘Consistency’. The OperationType ‘LocalConfigurationManager’ logs the configuration of the LCM.</blockquote>
The trigger fires after an update happens. When the pull server send the StatusData to the database, backslashes are removed and the characters indicating an array in JSON.

[code lang="sql"]
CREATE TRIGGER [dbo].[DSCStatusReportOnUpdate]
   ON  [dbo].[StatusReport] 
   AFTER UPDATE
AS
SET NOCOUNT ON
BEGIN
	DECLARE @JobId nvarchar(50) = (SELECT JobId FROM inserted);
	DECLARE @StatusData nvarchar(MAX) = (SELECT StatusData FROM inserted);
	IF @StatusData LIKE '\[%' ESCAPE '\'
		SET @StatusData = REPLACE(SUBSTRING(@StatusData, 3, Len(@StatusData) - 4), '\', '')

	DECLARE @Errors nvarchar(MAX) = (SELECT [Errors] FROM inserted);
	IF @Errors IS NULL
		SET @Errors = (SELECT Errors FROM StatusReport WHERE JobId = @JobId)
	
	IF @Errors LIKE '\[%' ESCAPE '\' AND Len(@Errors) &amp;amp;gt; 4
		SET @Errors = REPLACE(SUBSTRING(@Errors, 3, Len(@Errors) - 4), '\', '')

    UPDATE StatusReport
	SET StatusData = @StatusData, Errors = @Errors
	WHERE JobId = @JobId
	
END 
[/code]

After the conversion, SQL Server can parse and query the data in the column StatusData as JSON. This is the process of getting to the details in a JSON-formatted column.
<h3>How to retrieve data from a JSON column</h3>
This query is getting just the "StartDate" for all nodes. StartDate is a value in the JSON column "StatusData".

[code lang="sql"]
SELECT dbo.StatusReport.NodeName, JSON_VALUE([StatusReport].StatusData, '$.Status')
FROM dbo.StatusReport
[/code]

or

[code lang="sql"]
SELECT dbo.StatusReport.NodeName,(
	SELECT [Value] 
	FROM OPENJSON ([StatusData])
		WHERE [Key] = 'StartDate'
	) AS StartDate
FROM dbo.StatusReport
[/code]

The functions “tvfGetNodeStatus” and “tvfGetRegistrationData” are for combining the data accessible directly using SQL and what is store as JSON. The queries in these functions are more complex, as there is JSON stored in JSON stored in SQL.

These two functions can be access through the views “vRegistrationData” and “vNodeStatusComplex” and these views are used by PowerBI.
<h2>PowerBI - the interesting part</h2>
<h3>Starting with a new workbook</h3>
So, after having the database in place and some nodes registered and have them already send some feedback, it is time to make the data visible.

After opening PowerBI you will see a welcome screen. There is an option named "Get data". After clicking on that option, you will be guided through the process of setting up the connection. Please select "SQL Server database", then you have to provide a server and database name and decide if you want to import the data or use DirectQuery. After finishing that you should see a list of all available sources. Please select all the views here (v*) and click on load.

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_SelectSource.png"><img width="844" height="389" class="alignnone size-full wp-image-685" alt="powerbi_selectsource" src="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_SelectSource.png" /></a>

After finishing this step, you have to choose a visualization. Click on the Pie Chart and you get a grey template on your empty workbook. Keep the focus on the Pie Chart and in the Fields menu on the right side expand the node "vNodeStatusComplex". Please select from the field "Status" first and then "NodeName". This will move "Status" to "Legend" and "NodeName" to "Details".

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_PieChart1.png"><img width="885" height="580" class="alignnone size-full wp-image-695" alt="powerbi_piechart1" src="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_PieChart1.png" /></a>

Obviously, this does not work. Please drag and drop the "NodeName" from "Details" to "Values". Voila, if all your DSC nodes are compliant, you have now a Pie Chart with just one big piece. In my environment, 2 out of 8 does have problems so it looks like this:

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_PieChart2.png"><img width="884" height="598" class="alignnone size-full wp-image-705" alt="powerbi_piechart2" src="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_PieChart2.png" /></a>

&nbsp;
<h3>Using an existing workbook</h3>
If you want to get started quickly, you can download the PowerBI file <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/master/LabSources/PostInstallationActivities/SetupDscPullServer/DSC%20Dashboard.pbix" rel="noopener noreferrer">Dashboard.pbix</a>, the first screenshot has been taken from this file. You have to change the data source as your SQL Server pretty likely has a different name. Click on "Edit Queries -&gt; Data source settings" and then on "Change Source".

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_ChangeSource1.png"><img width="635" height="360" class="alignnone size-full wp-image-715" alt="powerbi_changesource1" src="https://msdnshared.blob.core.windows.net/media/2017/05/PowerBI_ChangeSource1.png" /></a>

&nbsp;
<h2>Summary</h2>
After walking through this article, you have a database on SQL Server 2016 that presents the node status data in a way that can be visualized with PowerBI or other tools. This moves DSC on a new level and should makes it more appealing for IT managers.]]></content:encoded>
    </item>
    <item>
      <title>Broken DSC Reporting, RequestEntityTooLarge and some LCM internals</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/broken-dsc-reporting-requestentitytoolarge-and-some-lcm-internals</link>
      <pubDate>Sat, 20 May 2017 00:43:01 GMT</pubDate>
      <dc:creator><![CDATA[Raimund Andrée]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=586</guid>
      <description><![CDATA[Abtract
After migrating a complex DSC configuration using partial configurations to composite...]]></description>
      <content:encoded><![CDATA[<h2>Abtract</h2>
After migrating a complex DSC configuration using <a href="https://msdn.microsoft.com/en-us/powershell/dsc/partialConfigs">partial configurations</a> to <a href="https://msdn.microsoft.com/en-us/powershell/dsc/authoringresourcecomposite">composite resources</a>, DSC reporting using SQL Server as described in <a href="https://blogs.technet.microsoft.com/fieldcoding/2017/05/11/using-sql-server-2016-for-a-dsc-pull-server/">Using SQL Server 2016 for a DSC Pull Server</a> did not work any longer or was no longer reliable. This article is about troubleshooting this and learning something about Event ID 4260 and how the LCM sends data to the reporting database.
<h3></h3>
<h2>The change that leads to the issue</h2>
The configuration for a node in my case is quite complex and consists of at least 200 configuration items, and the number is growing. To make the management of the configuration easier and being able to manage dependency, I needed to split the configuration into sections. Recently I have learned that the way to split large DSC configurations by means of <a href="https://msdn.microsoft.com/en-us/powershell/dsc/authoringresourcecomposite">composite resources</a>. Unfortunately, everything was build on <a target="_blank" href="https://msdn.microsoft.com/en-us/powershell/dsc/partialConfigs" rel="noopener noreferrer">partial configurations</a>.

The conversion was actually much easier than expected. Everything was working afterwards but one important part was missing: Reporting. Somehow the data send from the LCM to the pull server is bigger when using composite resources instead of partial configurations, even if the configuration that the client creates contains the same configuration items.

&nbsp;
<h2>The effect on the database</h2>
After not seeing any new data in the report, first I have taken a look at the database.

When the LCM starts applying a configuration (Start-DscConfiguration), it writes an entry of type 'Initial' into the reporting database. This record has a StartTime but not yet a valid EndTime. Also, StatusData only contains some basic information about the node.
<blockquote>Note: The OperationType 'Initial' indicates that the node applies a configuration the first time. All subsequent applications are logged with the OperationType 'Consistency'. The OperationType 'LocalConfigurationManager' logs the configuration of the LCM.</blockquote>
&nbsp;

[code lang="PowerShell"]
Invoke-Sqlcmd -Database DSC -Query 'SELECT TOP 1 * FROM StatusReport'
[/code]

<pre>JobId                : d7352efe-3ce2-11e7-836e-0017fa00000a
Id                   : 77baf262-3ca1-11e7-8365-0017fa00000a
OperationType        : Initial
RefreshMode          :
Status               :
LCMVersion           : 2.0
ReportFormatVersion  : 2.0
ConfigurationVersion :
NodeName             : XDC11
IPAddress            : 192.168.88.129;127.0.0.1;fe80::99e2:71d5:6f53:7fe0%5;::2000:0:0:0;::1;::2000:0:0:0
StartTime            : 5/20/2017 12:31:03 AM
EndTime              : 12/30/1899 12:00:00 AM
Errors               : []
StatusData           : ["{\"IPV4Addresses\":[\"192.168.88.129\",\"127.0.0.1\"],\"CurrentChecksum\":\"0926E211C6D8617558
                       55CED942CB734CC94BF620EC6E4F8D1B9889F5B57ED6C7\",\"MACAddresses\":[\"00-17-FA-00-00-0A\",\"00-00
                       -00-00-00-00-00-E0\"],\"Type\":\"Initial\",\"HostName\":\"XDC11\",\"Locale\":\"en-US\",\"StartDa
                       te\":\"2017-05-20T09:31:03.9100000+11:00\",\"JobID\":\"{D7352EFE-3CE2-11E7-836E-0017FA00000A}\",
                       \"LCMVersion\":\"2.0\",\"IPV6Addresses\":[\"fe80::99e2:71d5:6f53:7fe0%5\",\"::2000:0:0:0\",\"::1
                       \",\"::2000:0:0:0\"]}"]
RebootRequested      : False
AdditionalData       : [{"Key":"OSVersion","Value":"{\"VersionString\":\"Microsoft Windows NT 10.0.14393.0\",\"ServiceP
                       ack\":\"\",\"Platform\":\"Win32NT\"}"},{"Key":"PSVersion","Value":"{\"CLRVersion\":\"4.0.30319.4
                       2000\",\"PSVersion\":\"5.1.14393.0\",\"BuildVersion\":\"10.0.14393.0\"}"}]
</pre>
&nbsp;

After the LCM has finished the job, it sends an update statement with the same JobId to the pull server. The pull server then forwards this update query to the database server. In this update statement the LCM provides all the missing details like Status, StatusData and the EndTime.

In my case, this did not happen and the database was filling up with records of OperationType 'Initial' or 'Consistency' that did not have an EndTime. Additionally, all my nice report queries and PowerBI did not work anymore.

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/SqlStatusReportOnlyInitial.png"><img width="1024" height="226" class="alignnone size-large wp-image-595" alt="sqlstatusreportonlyinitial" src="https://msdnshared.blob.core.windows.net/media/2017/05/SqlStatusReportOnlyInitial-1024x226.png" /></a>

&nbsp;

One takeaway is that if something does not work with reporting, first take look at the DSC nodes operational log. I found an error with the ID 4260 there that was telling something about "RequestEntityTooLarge".

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/EventViewer4260.png"><img width="989" height="473" class="alignnone size-full wp-image-605" alt="eventviewer4260" src="https://msdnshared.blob.core.windows.net/media/2017/05/EventViewer4260.png" /></a>

&nbsp;

Of course, you can also use PowerShell to find these event log entries using Get-WinEvent.

[code lang="PowerShell"]
Get-WinEvent -FilterHashTable @{ LogName = 'Microsoft-Windows-Dsc/Operational'; ID = 4260 } | Select-Object -First 1 | Format-List
[/code]

<div>
<pre>TimeCreated  : 5/21/2017 6:32:10 AM
ProviderName : Microsoft-Windows-DSC
Id           : 4260
Message      : Job {03B1E172-3D93-11E7-836F-0017FA00000A} :
               Http Client 77BAF262-3CA1-11E7-8365-0017FA00000A failed for WebReportManager for configuration The
               attempt to send status report to the server https://xpull1:8080/PSDSCPullServer.svc/Nodes(AgentId='77BAF
               262-3CA1-11E7-8365-0017FA00000A')/SendReport returned unexpected response code RequestEntityTooLarge..
</pre>
&nbsp;

The problem are some limitations on the pull server's web configuration. After some research and testing I was confused by contradictory statements and resolutions. After trying without success for quite some time, I finally got it working by adding this into the web.config in 'C:\inetpub\PSDSCPullServer'.

[code lang="xml"]
&lt;bindings&gt;
  &lt;webHttpBinding&gt;
    &lt;binding maxBufferPoolSize=&quot;2147483647&quot; maxReceivedMessageSize=&quot;2147483647&quot; maxBufferSize=&quot;2147483647&quot; transferMode=&quot;Streamed&quot;&gt;
    &lt;/binding&gt;
  &lt;/webHttpBinding&gt;
&lt;/bindings&gt;
[/code]

&nbsp;

Unfortunately the xWebConfigKeyValue does not yet support doing these settings when <a target="_blank" href="https://msdn.microsoft.com/en-us/powershell/dsc/pullServer" rel="noopener noreferrer">setting up the DSC pull server using DSC</a>. As I do not want to make this a manual step that I can forget, I have added these lines into the pull server's setup routine.

[code lang="PowerShell"]
$path = 'C:\inetpub\PSDSCPullServer\web.config'

$doc = [[xml]](Get-Content -Path $path)
$serviceModelNode = ($doc | Select-Xml -XPath '//system.serviceModel').Node

$bindingsNode = ($doc | Select-Xml -XPath '//system.serviceModel/bindings').Node
if (-not $bindingsNode)
{
    $bindingsNode = $doc.CreateElement('bindings')
    [void]$serviceModelNode.AppendChild($bindingsNode)
}

$webHttpBindingNode = ($doc | Select-Xml -XPath '//system.serviceModel/bindings/webHttpBinding').Node
if (-not $webHttpBindingNode)
{
    $webHttpBindingNode = $doc.CreateElement('webHttpBinding')
    [void]$bindingsNode.AppendChild($webHttpBindingNode)
}

$bindingNode = ($doc | Select-Xml -XPath '//system.serviceModel/bindings/webHttpBinding/binding').Node
if (-not $bindingNode)
{
    $bindingNode = $doc.CreateElement('binding')
    [void]$webHttpBindingNode.AppendChild($bindingNode)
}

$bindingNode.SetAttribute('maxBufferPoolSize', [int]::MaxValue)
$bindingNode.SetAttribute('maxReceivedMessageSize', [int]::MaxValue)
$bindingNode.SetAttribute('maxBufferSize', [int]::MaxValue)
$bindingNode.SetAttribute('transferMode', 'Streamed')

$doc.Save($path)
[/code]

&nbsp;

After this change, it requires restarting the pull server's app pool. The easiest way is calling iisreset.exe.

</div>
<h2>Reporting works again</h2>
After having altered the web.config and restarted the app pool, the event ID 4260 on the DSC nodes got replaced by 4266, which looks much better.

<img width="1024" height="366" class="alignnone size-large wp-image-615" alt="eventviewer4266" src="https://msdnshared.blob.core.windows.net/media/2017/05/EventViewer4266-1024x366.png" />

&nbsp;

Also the records in the database are being updated again, as you can see by the valid EndTime.
<blockquote>Note: What is not visible in the screenshots is the column StatusData which stores the most interesting data.</blockquote>
<a href="https://msdnshared.blob.core.windows.net/media/2017/05/SqlStatusReportComplete.png"><img width="1024" height="173" class="alignnone size-large wp-image-635" alt="sqlstatusreportcomplete" src="https://msdnshared.blob.core.windows.net/media/2017/05/SqlStatusReportComplete-1024x173.png" /></a>

&nbsp;
<h2>Some Richer Reporting</h2>
If you create the database with the script references in <a href="https://blogs.technet.microsoft.com/fieldcoding/2017/05/11/using-sql-server-2016-for-a-dsc-pull-server/">SQL Server 2016 for a DSC Pull Server</a> you have a view that parses the Json data and returns also some valuable information like Duration (how long has it taken applying the configuration) and how many resources are or are not in desired state. The non-compliant resources are also names in the column ResourceIdsNotInDesiredState.

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/SqlFilledRawStatusData.png"><img width="1024" height="368" class="alignnone size-large wp-image-645" alt="sqlfilledrawstatusdata" src="https://msdnshared.blob.core.windows.net/media/2017/05/SqlFilledRawStatusData-1024x368.png" /></a>

I hope this helps. Have fun with PowerShell and DSC.]]></content:encoded>
    </item>
    <item>
      <title>PowerShell Tip: Getting enum values as names, int and bit</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/powershell-tip-getting-enum-values-as-names-int-and-bit</link>
      <pubDate>Thu, 18 May 2017 16:44:16 GMT</pubDate>
      <dc:creator><![CDATA[Raimund Andrée]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=576</guid>
      <description><![CDATA[There are a number of samples how to resolve enums and display all the possible values. However,...]]></description>
      <content:encoded><![CDATA[There are a number of samples how to resolve enums and display all the possible values. However, none of those worked for me with the enum System.Security.AccessControl.FileSystemRights. This enum has duplicate values and as most functions listing enum values are based on <a href="https://msdn.microsoft.com/en-us/library/system.enum.getvalues(v=vs.110).aspx" target="_blank">Enum.GetValues</a>, you see not just the duplicate values but also duplicate names. So while sitting in an airplane in Cologne waiting for the staff to fix some valve, i couldn’t think of a better way to spend my time than writing about this world-changing code snippet.

To solve that, I have created this little helper function, an ideal candidate for your PowerShell profile. It uses <a href="https://msdn.microsoft.com/en-us/library/system.enum.getnames(v=vs.110).aspx" target="_blank">Enum.GetNames</a> hence shows the names as defined in the enum regardless if they have the same value. Get-Enum prints the binary values if you pass in a Flags enum. If the enum is not a binary one, the output will be only name and value.

[code lang="powershell"]
function Get-Enum { 
    param (
        [type]$Type
    )

    if ($Type.BaseType.FullName -ne 'System.Enum')
    {
        Write-Error &quot;Type '$Type' is not an enum&quot;
        return
    }

    if ($Type.CustomAttributes | Where-Object { $_.AttributeType -eq [System.FlagsAttribute] })
    {
        Write-Host &quot;Type '$Type' is a Flags enum&quot;
        Write-Host
        $isFlagsEnum = $true
    }

    $props = @(
        @{ Name = 'Name'; Expression={ [string]$_ } }
        @{ Name = 'Value'; Expression={ [uint32](Invoke-Expression &quot;[$($type.FullName)]'$_'&quot;) }}
    )

    if ($isFlagsEnum)
    {
        $props += @{ Name = 'Binary'; Expression={[Convert]::ToString([uint32](Invoke-Expression &quot;[$($type.FullName)]'$_'&quot;), 2)}}
    }

    [enum]::GetNames($Type) | 
    Select-Object -Property $props    
} 
[/code]

&nbsp;

You can call the function like this

[code lang="powershell"]
Get-Enum System.DayOfWeek
[/code]

to get this output:

[code]
Name      Value
----      -----
Sunday        0
Monday        1
Tuesday       2
Wednesday     3
Thursday      4
Friday        5
Saturday      6
[/code]

&nbsp;

And this is the example of the enum that made me write this post

[code lang="powershell"]
Get-Enum -Type System.Security.AccessControl.FileSystemRights
[/code]

And as this is a flags enum, the function displays the binary representation of the value as well:

[code]
Name                           Value Binary               
----                           ----- ------               
ListDirectory                      1 1                    
ReadData                           1 1                    
WriteData                          2 10                   
CreateFiles                        2 10                   
CreateDirectories                  4 100                  
AppendData                         4 100                  
ReadExtendedAttributes             8 1000                 
WriteExtendedAttributes           16 10000                
Traverse                          32 100000               
ExecuteFile                       32 100000               
DeleteSubdirectoriesAndFiles      64 1000000              
ReadAttributes                   128 10000000             
WriteAttributes                  256 100000000            
Write                            278 100010110            
Delete                         65536 10000000000000000    
ReadPermissions               131072 100000000000000000   
Read                          131209 100000000010001001   
ReadAndExecute                131241 100000000010101001   
Modify                        197055 110000000110111111   
ChangePermissions             262144 1000000000000000000  
TakeOwnership                 524288 10000000000000000000 
Synchronize                  1048576 100000000000000000000
FullControl                  2032127 111110000000111111111
[/code]

Have fun!]]></content:encoded>
    </item>
    <item>
      <title>Using SQL Server 2016 for a DSC Pull Server</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/using-sql-server-2016-for-a-dsc-pull-server</link>
      <pubDate>Thu, 11 May 2017 11:29:51 GMT</pubDate>
      <dc:creator><![CDATA[Raimund Andrée]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=535</guid>
      <description><![CDATA[Abstract
This article describes how to use a SQL Server 2016 as the backend database for a Desired...]]></description>
      <content:encoded><![CDATA[<h2>Abstract</h2>
This article describes how to use a SQL Server 2016 as the backend database for a Desired State Pull Server. The default database engine is EDB and lacks a couple of feature required. It is quite hard to create reports based on the EDB and using the web service to retrieve data can get extremely slow. This is why we needed another solution for DSC reporting.

This article guides you through the setup process.
<h2>Credits</h2>
This article is inspired by <a target="_blank" href="https://leandrowpblog.wordpress.com/2016/10/26/using-sql-server-db-for-dsc/" rel="noopener noreferrer">Using SQL Server DB for DSC</a> and adds more automation and a better security model.
Also worth a look the post <a target="_blank" href="https://blogs.msdn.microsoft.com/wushuai/2016/11/27/try-to-use-sql-server-as-dsc-pull-server-backend/" rel="noopener noreferrer">Use SQL Server as DSC Pull Server Backend?</a>. It explains why you cannot use SQL Server directly and have to go over the MDB.
<h2>Requirements</h2>
<strong>You need to have a good understanding about DSC in general and how to setup a DSC pull server.</strong>

To be able to successfully execute all the steps described here, you need a domain joined DSC pull server and a SQL Server 2016 joined to the same forest. One or more machines that will become DSC nodes are also quite helpful.

If you want to setup the requirements automatically, look at <a target="_blank" href="https://github.com/AutomatedLab/automatedLab" rel="noopener noreferrer">AutomatedLab</a> and the script <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/master/SampleScripts/Scenarios/DSC%20Pull%20Scenario%201%20(Pull%20Configuration).ps1" rel="noopener noreferrer">DSC Pull Scenario 1 (Pull Configuration).ps1</a>. Only the SQL Server 2016 is missing in this deployment script but can be added with just to lines:

[code lang="powershell"]
Add-LabIsoImageDefinition -Name SQLServer2016 -Path $labSources\ISOs\en_sql_server_2016_standard_with_service_pack_1_x64_dvd_9540929.iso

Add-LabMachineDefinition -Name DSQL1 -Roles SQLServer2016
[/code]

If you want to redeploy the scenario that this article describes, you can use the script <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/master/LabSources/PostInstallationActivities/SetupDscPullServer/CreateDscSqlDatabase.ps1" rel="noopener noreferrer">DSC Pull Scenario 1 with SQL Server.ps1</a>. It takes care of the requirements plus all what is described in this post fully automated. So, if you just need a lab with a DSC pull server that stores all data on SQL, this can save you a huge amount of time.

&nbsp;
<h2>Setting things ready on the SQL Server and connecting the pull server</h2>
On the SQL Server, you need to create the database for DSC and login for the computer account.
<blockquote>Note: The article mentioned in the credits section is great and saved me a lot of time. But one thing I did not like is providing credentials if not absolutely required. The initial approach used credentials stored along with the OleDB connecting in the registry. The setup described here works differently and gives the computer account of the DSC pull server write access to the database.</blockquote>
<h3></h3>
<h3>Creating the DB on the SQL Server</h3>
<blockquote>Note: All tasks in this section are executed on the SQL Server</blockquote>
The SQL script to create the database is published on <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/master/LabSources/PostInstallationActivities/SetupDscPullServer/CreateDscSqlDatabase.ps1" rel="noopener noreferrer">CreateDscDatabase.ps1</a>. It asks for a domain name and a computer name, creates a SQL Server login for that user and gives this login write access to the DSC database.

The table setup is almost like described in <a target="_blank" href="https://leandrowpblog.wordpress.com/2016/10/26/using-sql-server-db-for-dsc/" rel="noopener noreferrer">Using SQL Server DB for DSC</a>. There is only one additional column called AdditionalData in the table StatusReport.

Along with the tables there is a trigger to transform the data into readable JSON and a couple of functions and views to get a report of your DSC nodes. Details about this will be covered in the next article.

Please just run the script on the SQL Server like this by providing the domain name and the computer account name of the pull server.

[code lang="powershell"]
C:\CreateDscDatabase.ps1 -DomainName contoso -ComputerName dpull1
[/code]

&nbsp;

The output should look like this:

&nbsp;

[code]
Creating the DSC database on the local default SQL instance...finished.
Database is stored on C:\DSCDB
[/code]

&nbsp;
<h3>Connecting the DSC Pull Server with the SQL database</h3>
<blockquote>Note: All tasks in this section are executed on the Pull Server</blockquote>
<h4>The pull server must be configured to use MDB instead of the default EDB database</h4>
If not already done, please change the database model for the pull server from edb to mdb. If you deploy the pull server with AutomatedLab and the script mentioned earlier, the database model is already set to MDB. If you want to configure the pull server by yourself or want to change the database model after the deployment, you may want to use this DSC config file: <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/master/LabSources/PostInstallationActivities/SetupDscPullServer/SetupDscPullServerMdb.ps1" rel="noopener noreferrer">SetupDscPullServerMdb.ps1</a>.

If everything is configured correctly, you should see these entries in the C:\inetpub\PSDSCPullServer\web.config file:

&nbsp;

[code lang="xml"]
&lt;add key=&quot;dbprovider&quot; value=&quot;System.Data.OleDb&quot;&gt;
&lt;add key=&quot;dbconnectionstr&quot; value=&quot;Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program Files\WindowsPowerShell\DscService\Devices.mdb;&quot;&gt;
[/code]

&nbsp;
<h4>ODBC Data Source</h4>
The MDB that we will use in the next step requires an OleDB connection named "DSC" which needs to point to the SQL Server that you have created the DSC database previously. You can create the data source either using the <a target="_blank" href="https://docs.microsoft.com/en-us/sql/odbc/admin/odbc-data-source-administrator" rel="noopener noreferrer">ODBC Data Source Administrator</a> or PowerShell. The command for that is:

&nbsp;

[code lang="powershell"]
Add-OdbcDsn -Name DSC -DriverName 'SQL Server' -Platform '32-bit' -DsnType System -SetPropertyValue @('Description=DSC Pull Server', &quot;Server=&lt;Name of your SQL Server&gt;&quot;, 'Trusted_Connection=yes', 'Database=DSC') -PassThru
[/code]

&nbsp;
<h4>Replacing the MDB with a version that is using "linked tables"</h4>
After changing the database model to MDB, the pull server writes all information into that file. However, the purpose of this article is to have the data in a SQL database. To achieve that, the MDB (C:\Program Files\WindowsPowerShell\DscService\Devices.mdb) should be replaced by this version <a target="_blank" href="https://github.com/AutomatedLab/AutomatedLab/blob/master/LabSources/PostInstallationActivities/SetupDscPullServer/Devices.mdb" rel="noopener noreferrer">Devices.mdb</a>. This MDB uses <a target="_blank" href="https://support.office.com/en-us/article/Import-or-link-Access-to-SQL-Server-data-EA1B29BE-8C57-465E-AC35-18E3225ED9D5" rel="noopener noreferrer">linked tables</a> to redirect the data to the SQL Server. If you are not sure what happens inside the file, just open it with Access and you will see the tables but no data yet on the right side, as there is nothing in the DSC database on the SQL Server.
<blockquote>Note: If you feel uncomfortable using the mdb that someone else uploaded, you can very easily create the file by yourself. The steps are described in <a target="_blank" href="https://leandrowpblog.wordpress.com/2016/10/26/using-sql-server-db-for-dsc/" rel="noopener noreferrer">Using SQL Server DB for DSC</a>.</blockquote>
<a href="https://msdnshared.blob.core.windows.net/media/2017/05/Access1.png"><img width="1024" height="472" class="alignnone size-large wp-image-545" alt="access1" src="https://msdnshared.blob.core.windows.net/media/2017/05/Access1-1024x472.png" /></a>
<h2>Testing the setup</h2>
This section covers a very easy test configuration and configures some machines to pull the configuration from the pull server. In order to do that data has to be written in the SQL database.

&nbsp;
<h3>Defining the configuration</h3>
The configuration is just making sure that the given file has the content "OK". The node name is "localhost" so it applies to each machine that pulls it.

&nbsp;

[code lang="powershell"]
Configuration 'TestConfig'
{
    Import-DscResource –ModuleName 'PSDesiredStateConfiguration'
    
    Node localhost
    {
        File 'TestFile'
        {
            Ensure = 'Present'
            Type = 'File'
            DestinationPath = 'C:\DscTestFile'
            Contents = 'OK'
        }
    }
}
[/code]

&nbsp;
<h3>Converting the configuration into a MOF</h3>
The <a target="_blank" href="https://msdn.microsoft.com/en-us/PowerShell/DSC/metaConfig" rel="noopener noreferrer">local configuration manager</a> cannot use DSC configuration written in PowerShell directly. You have to build a MOF first.

&nbsp;

[code lang="powershell"]
TestConfig -OutputPath C:\DscTestConfig | Out-Null
[/code]

&nbsp;

The MOF file always get the name of the given node, in this case "localhost.mof". This is not the best name for the configuration so the file is renamed to "TestConfig.mof".

&nbsp;

[code lang="powershell"]
Rename-Item -Path C:\DscTestConfig\localhost.mof -NewName Rename-Item -Path C:\DscTestConfig\localhost.mof -NewName TestConfig.mof
[/code]

&nbsp;

&nbsp;
<h3>Publishing the MOF</h3>
Now you are finished creating the MOF. But the pull server does not know about it hence clients cannot pull the MOF. Publishes configurations (MOFs) need to be in the folder C:\Program Files\WindowsPowerShell\DscService\Configuration along with a checksum. The easiest way to achieve this is by calling the following cmdlet.

&nbsp;

[code lang="powershell"]
Publish-DSCModuleAndMof -Source C:\DscTestConfig
[/code]

&nbsp;
<h3>Telling the DSC nodes to pull the TestConfig</h3>
So far, no computer in the lab knows about the pull server. To introduce the pull server to the nodes, you must configure the local configuration manager, of course also using DSC. The following configuration does all this.
<blockquote>Note: The attribute DSCLocalConfigurationManager indicates that a special kind of configuration called meta configuration should be created. Meta configurations are configuring the LCM and have the extension .meta.mof. More info on this can be found in <a target="_blank" href="https://msdn.microsoft.com/en-us/PowerShell/DSC/metaConfig" rel="noopener noreferrer">Configuring the Local Configuration Manager</a>.</blockquote>
&nbsp;

[code lang="powershell"]
[DSCLocalConfigurationManager()]
Configuration DscClientConfig
{
    param(
        [Parameter(Mandatory)]
        [string]$PullServer,

        [Parameter(Mandatory)]
        [string]$RegistrationKey,

        [Parameter(Mandatory)]
        [string[]]$ComputerName,

        [Parameter(Mandatory)]
        [string]$ConfigurationName
    )
    
    Node $ComputerName
    {
        Settings
        {
            RefreshMode          = 'Pull'
            RefreshFrequencyMins = 30
            ConfigurationModeFrequencyMins = 15
            ConfigurationMode = 'ApplyAndAutoCorrect'
            RebootNodeIfNeeded   = $true
        }
        
        ConfigurationRepositoryWeb PullServer
        {
            ServerURL          = &quot;https://$($PullServer):8080/PSDSCPullServer.svc&quot;
            RegistrationKey    = $RegistrationKey
            ConfigurationNames = $ConfigurationName
        }
        
        ReportServerWeb ReportServer
        {
            ServerURL       = &quot;https://$($PullServer):8080/PSDSCPullServer.svc&quot;
            RegistrationKey = $RegistrationKey
        }
    }
}
[/code]

&nbsp;

Now this meta configuration needs to be built and finally pushed to the DSC nodes. The following code does this for all the computers that are in the computers container in Active Directory. The registration key is a GUID you have to generate and provide when setting up the pull server. This GUID is written to a text file that is required for the next step.

&nbsp;

[code lang="powershell"]
$computerContainerDn = &quot;CN=Computers,$((Get-ADRootDSE).defaultNamingContext)&quot;
$computerName = (Get-ADComputer -SearchBase $computerContainerDn -Filter *).DnsHostName
$registrationKey = Get-Content -Path 'C:\Program Files\WindowsPowerShell\DscService\RegistrationKeys.txt'

DscClientConfig -OutputPath c:\DscClientConfig -PullServer $env:COMPUTERNAME -RegistrationKey $registrationKey -ComputerName $computerName -ConfigurationName TestConfig

Set-DscLocalConfigurationManager -Path C:\DscClientConfig -ComputerName $computerName
Start-DscConfiguration -UseExisting -ComputerName $computerName -Wait -Verbose
[/code]

&nbsp;
<h2>Verifying the data on the SQL Server</h2>
&nbsp;

Now it is time to open the SQL Server Management Studio on the SQL Server and navigate to the DSC database, then to the table "RegistrationData". Do a right-click and then click on "Select Top 1000 Rows". You should see data like this indicating that everything works as expected.

<a href="https://msdnshared.blob.core.windows.net/media/2017/05/SQL-RegistrationData.png"><img width="1024" height="486" class="alignnone size-large wp-image-546" alt="sql-registrationdata" src="https://msdnshared.blob.core.windows.net/media/2017/05/SQL-RegistrationData-1024x486.png" /></a>

&nbsp;
<h2>Conclusion</h2>
It looks more complicated as it is to move the DSC data to a SQL Server. Still, this is a workaround and I hope that in the future we can provide a connection string for SQL Server in the web.config directly (dbconnectionstr).

Having the data now on SQL gives you a universe of tools to do reporting and data hygiene tasks. In this article, I have used SQL Server 2016 as it natively supports JSON which is important as the pull server stores JSON in SQL columns.

The next article will cover advanced reporting with the data you have in SQL Server now.]]></content:encoded>
    </item>
    <item>
      <title>Privileged Access Management - demystified</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/privileged-access-management-demystified</link>
      <pubDate>Tue, 09 May 2017 21:37:05 GMT</pubDate>
      <dc:creator><![CDATA[Michael Frommhold MSFT]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/?p=475</guid>
      <description><![CDATA[&nbsp;
Today's topic: 
Privileged Access Management (PAM)
Just in Time...]]></description>
      <content:encoded><![CDATA[&nbsp;
<p style="margin: 0in 0in 10pt"><span style="font-size: medium;font-family: calibri">Today's topic: </span></p>
<p align="center" style="text-align: center;margin: 0in 0in 10pt"><b><span style="font-size: 12pt;font-family: 'Cambria',serif;color: #4f6228;line-height: 115%"><span style="font-size: medium"><span style="font-family: Calibri">Privileged Access Management (PAM)</span></span></span></b></p>
<p align="center" style="text-align: center;margin: 0in 0in 10pt"><b><span style="font-size: 12pt;font-family: 'Cambria',serif;color: #4f6228;line-height: 115%"><span style="font-size: medium"><span style="font-family: Calibri">Just in Time Administration</span></span></span></b></p>
<p align="center" style="text-align: center;margin: 0in 0in 10pt"><b><span style="font-size: 12pt;font-family: 'Cambria',serif;color: #4f6228;line-height: 115%"><span style="font-size: medium"><span style="font-family: Calibri">demystified
</span></span></span></b></p>
<span style="font-size: medium;font-family: calibri"></span>

<span style="color: #333333">Coming with Microsoft Server 2016, we offered a new optional feature for Active Directory: the 'Privileged Access Management Feature'. The new feature is only available with Domain Controllers OS &gt;= Windows Server 2016.</span>

<span style="color: #333333">Privileged Access Management Feature consists of two parts:</span>
<ol>
 	<li><span style="color: #333333">Privileged Access Management (PAM) – aka Privileged Identity Management (PIM)</span></li>
 	<li><span style="color: #333333">Just in Time Administration (JiTA)</span></li>
</ol>
<span style="color: #333333">PAM represents an extra layer of security for administration – basically it’s a sort of ‘security by obscurity’ in its most positive interpretation.</span>

<span style="color: #333333">  </span>

<span style="color: #333333"><strong><u>How does it work (conceptually):</u></strong></span>

<span style="color: #333333">After implementing a single domain forest as admin forest, trusted by your production forest, you create shadow principals in the admin forest, that bear the SIDs of administrative groups from the production forest with it, for example from the Enterprise Admins and the Domain Admins.
</span><span style="color: #333333">You may then add user objects from the admin forest to the member attribute of the shadow principals. If such an user object logs on to resource in the production forest it will carry the SIDs of the administrative groups that correspond to the shadow principals user object is member of.
Note: All types of security principals my be mirrored (-&gt; all objects that have an objectSid).
<span style="color: #333333">One of the nice effects is that you can call DCpromo from GUI with an account from your admin forest that has the production Domain Admins shadow group membership and will therefore successfully pass the DCPromo internal check for Domain Admins group membership.
</span></span>

<span style="color: #333333">This happens because the Key Distribution Center (KDC) service on a Domain Controller calculates the SID list of an user’s group memberships provided by the forest this DC is member of - from:</span>
<ul>
 	<li><span style="color: #333333">sIDHistory</span></li>
 	<li><span style="color: #333333">direct group memberships ( + the groups sIDHistory)</span></li>
 	<li><span style="color: #333333">nested group memberships ( + the groups sIDHistory)</span></li>
 	<li><span style="color: #333333">primaryGroupID ( –&gt; member of = &lt;Sid of the user’s domain + primaryGroupID&gt;)</span></li>
 	<li><span style="color: #333333">some default SIDs </span></li>
 	<li><span style="color: #333333">memberships in shadow principals (the corresponding SIDs of the shadowed groups)
This part only happens on DCs with OS &gt;= Windows Server 2016.</span></li>
</ul>
<span style="color: #333333">If the trust created between production and admin forest has the following trust attributes bits set</span>
<ul>
 	<li><span style="color: #333333">enable sIDHistory</span></li>
 	<li><span style="color: #333333">enable PIM trust</span></li>
</ul>
<span style="color: #333333">, the SIDs from the production forest in the calculated token make their way through the trust and thus will be present in the logon token in production.
</span>

<span style="color: #333333">Saying – you utilize user accounts from your admin forest, that 'pretend' to be member of the Domain Admins or Enterprise Admins from the production forest, but they <u>never where</u>, <u>are not</u> or <u>will not be</u> actual member of the production Domain Admins or Enterprise Admins.
This means that it is not possible to identify  these user accounts as high-level admin accounts by querying the well known high-level administrative groups –&gt; 'security by obscurity' :-)</span>
You may assign user accounts to the member attribute of groups or shadow principals permanently or temporary for a defined time span. If you assign membership temporary –&gt; this is where Just in Time Administration joins the game.

<strong>Note:</strong> JiTA is not limited to be only used with shadow principals over a PIM trust – you may use this in your forest / domain with normal group memberships as well.

A temporary membership is flagged with an expiration time stamp, defined by the time of assigning the membership plus the time span defined for the membership.
<span style="color: #333333">The Active Directory service (NTDS) on a Domain Controller with OS &gt;= Windows Server 2016 inspects memberships and removes these memberships after expiration time is exceeded.</span>

<span style="color: #333333">Additionally does the Key Distribution Center (KDC) on such a DC set the issued Ticket Granting Ticket’s (TGT) End Time to the earliest expiration time of a membership (if there is any membership with expiration time present for the user). Session tickets will have the same End Time set.</span>

<span style="color: #333333">Saying – if the expiration time of a membership exceeded –&gt;</span>
<ul>
 	<li><span style="color: #333333">the user’s membership to this group or shadow principal is revoked</span></li>
 	<li><span style="color: #333333">the TGT and </span><span style="color: #333333">the session tickets have expired</span></li>
 	<li><span style="color: #333333">user requests new TGT and session tickets when contacting the resources again –&gt; the membership is not in the token anymore
</span><span style="color: #333333"></span></li>
</ul>
<span style="color: #333333"><strong><u>How does it work (technically):</u></strong></span>

<span style="color: #333333">Mandatory prerequisites are:</span>
<ul>
 	<li><span style="color: #333333">the DCs  in the forest where you want to utilize JiTA / PAM are Windows Server 2016 (or higher) machines</span></li>
 	<li><span style="color: #333333">Forest Functional Level and Domain Functional Level of the admin forest must be of 'Windows Server 2016'</span></li>
 	<li><span style="color: #333333">for PAM usage:</span>
<ul>
 	<li><span style="color: #333333">a forest trust between production forest and admin forest (production trusts admin)</span></li>
 	<li><span style="color: #333333">production forest DCs must be at least Windows Server 2012 R2 machines</span></li>
 	<li><span style="color: #333333">if production forest </span><span style="color: #333333">DCs are Windows Server 2012 R2 machines must have May 2016 Update Rollup installed – easy check:
run <span style="font-family: Consolas;font-size: small">netdom trust /?
</span>if you see <span style="font-family: Consolas;font-size: small">/EnablePIMTrust</span> as last help topic –&gt; the patch is there.</span></li>
 	<li><span style="color: #333333"></span><span style="color: #333333">trustAttributes bit TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL (enable sIDHistory) is set in production for this trust</span></li>
 	<li><span style="color: #333333">trustAttributes bit TRUST_ATTRIBUTE_PIM_TRUST (enable PIM trust) is set in production for this trust</span></li>
 	<li><span style="color: #333333">optional feature 'Privileged Access Management Feature' is enabled in admin forest
</span></li>
</ul>
</li>
</ul>
<span style="color: #333333">The shadow principal object must be of objectClass msDs-ShadowPrincipal. The SID of the administrative group in your production forest is stored in the msDs-ShadowPrincipalSid attribute of the shadow principal.
Mandatory attributes while creation of a msDs-ShadowPrincipal object are:</span>
<ul>
 	<li><span style="color: #333333">cn</span></li>
 	<li><span style="color: #333333"></span>msDs-ShadowPrincipalSid</li>
</ul>
Such shadow principals can only be created as child objects of a msDs-ShadowPrincipalContainer object.

Shadow principals <u>must</u> be created in the default shadow principal container ' <span style="font-size: medium">Shadow Principal Configuration'</span>, located in the Services container, child of the configuration naming context of your admin forest – exemplary path for contoso.com:
CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso,DC=com

<strong>Note: </strong>Even though you may create msDs-ShadowPrincipalContainer in your domain naming context and therefore be able to create shadow principals here – they will not be functional for the PAM purpose.

To create a shadow principal I recommend to use PowerShell (PoSh) – the standard AD-Cmdlets are sufficient to perform the needed tasks. You will find sample code below.

Adding an user account to a shadow principal is a standard LDAP add modification request for the member attribute –&gt;
AddRequest(<em><span style="color: #0000ff">distinguishedName of the shadow principal</span></em>,member=<em><span style="color: #9b00d3">distinguishedName of the user</span></em>)

If we want to add an user account for a defined time span only – we use a little extension in the add modification request –&gt;
AddRequest(<em><span style="color: #0000ff">distinguishedName of the shadow principal</span></em>,member=&lt;TTL=<em><span style="color: #ff0000">time span in seconds</span></em><strong><span style="color: #333333">,</span></strong><em><span style="color: #9b00d3">distinguishedName of the user</span></em><span style="color: #333333">&gt;)</span>

There is PoSh sample code below.

<strong>Note:</strong> <span style="color: #333333">If you update an already assigned temporary membership, LDAP will not throw an 'already in group' exception, but just calculate the new expiration time for the entry.</span>

&nbsp;

<strong><span style="color: #333333"><u>How it's stored in the Active Directory database:</u></span></strong>

<span style="color: #333333">As a quick starter:
</span>We do not store data by its data base path (distinguishedName) but by an incrementing numeric unique identifier. These identifiers, which are called Distinguished Name Tags (DNTs), are not replicated or otherwise visible to other domain controllers.
As objects are created (directly or from replication) on a domain controller, a unique value is used.

<span style="color: #333333">The table below shows the data tables in the AD database and their usage.</span>
<table width="977" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="291">
<p align="left"><strong>Table</strong></p>
</td>
<td width="134">
<p align="left"><strong>Tree</strong></p>
</td>
<td width="206">
<p align="left"><strong>Data</strong></p>
</td>
<td width="428">
<p align="left"><strong>Usage</strong></p>
</td>
</tr>
<tr>
<td width="291">datatable</td>
<td width="134"></td>
<td width="206">column, index</td>
<td width="428">objects -&gt; attributes</td>
</tr>
<tr>
<td width="291">datatable</td>
<td width="134">long_table</td>
<td width="206">column, index</td>
<td width="428">big data attributes</td>
</tr>
<tr>
<td width="291">hiddentable</td>
<td width="134"></td>
<td width="206">column</td>
<td width="428">like system table in SQL</td>
</tr>
<tr>
<td width="291">link_table</td>
<td width="134"></td>
<td width="206">column, index</td>
<td width="428">linked attribute pair assoc</td>
</tr>
<tr>
<td width="291">link_history_table</td>
<td width="134"></td>
<td width="206">column, index</td>
<td width="428">linked attribute pair assoc history</td>
</tr>
<tr>
<td width="291">sd_table</td>
<td width="134"></td>
<td width="206">column, index</td>
<td width="428">single instance storage for Security Descriptors</td>
</tr>
<tr>
<td width="291">sdproptable</td>
<td width="134"></td>
<td width="206">column, index</td>
<td width="428">sd_table properties</td>
</tr>
<tr>
<td width="291">sdpropcounttable</td>
<td width="134"></td>
<td width="206">column</td>
<td width="428">sd_table properties count</td>
</tr>
<tr>
<td width="291">quota_table</td>
<td width="134"></td>
<td width="206">column, index</td>
<td width="428">AD quotas</td>
</tr>
<tr>
<td width="291">Quota_rebuild_progress_table</td>
<td width="134"></td>
<td width="206">column</td>
<td width="428">AD quota progress</td>
</tr>
</tbody>
</table>
&nbsp;

Structure of the datatable – for contoso.com:
<table width="624" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="92">
<p align="left"><strong>DNT</strong></p>
</td>
<td width="104">
<p align="left"><strong>Parent DNT</strong></p>
</td>
<td width="294">
<p align="left"><strong>RelativeDN</strong></p>
</td>
<td width="132"><strong>RDNType</strong></td>
</tr>
<tr>
<td width="92">0</td>
<td width="104">null</td>
<td width="294">com</td>
<td width="132">DC</td>
</tr>
<tr>
<td width="92">1</td>
<td width="104">0</td>
<td width="294">contoso</td>
<td width="132">DC</td>
</tr>
<tr>
<td width="92">165</td>
<td width="104">1</td>
<td width="294">Configuration</td>
<td width="132">CN</td>
</tr>
<tr>
<td width="92">212</td>
<td width="104">165</td>
<td width="294">Services</td>
<td width="132">CN</td>
</tr>
<tr>
<td width="92">287</td>
<td width="104">212</td>
<td width="294">Shadow Principal Configuration</td>
<td width="132">CN</td>
</tr>
<tr>
<td width="92">2721</td>
<td width="104">1</td>
<td width="294">GoldAdmins</td>
<td width="132">OU</td>
</tr>
<tr>
<td width="92">3436</td>
<td width="104">287</td>
<td width="294">ShadowDA.production</td>
<td width="132">CN</td>
</tr>
<tr>
<td width="92">3445</td>
<td width="104">2721</td>
<td width="294">GoldAdmin.production</td>
<td width="132">CN</td>
</tr>
</tbody>
</table>
<span style="color: #333333"> </span>

<span style="color: #333333">The distinguishedName of an object is calculated by following the Parent DNT chain and combining RDNType and RelativeDN– examples:</span>
<ul>
 	<li><span style="color: #333333">User account to be used for administration in production forest:
CN=GoldAdmin.production,OU=GoldAdmins,DC=contoso,DC=com</span>
<ul>
 	<li>DNT = 3445 –&gt; DN: CN=GoldAdmin.production</li>
 	<li>PDNT = 2721 –&gt; DN: CN=GoldAdmin.production,OU=GoldAdmins</li>
 	<li>PDNT = 1 –&gt; DN: CN=GoldAdmin.production,OU=GoldAdmins,DC=contoso</li>
 	<li>PDNT = 0 –&gt; DN: CN=GoldAdmin.production,OU=GoldAdmins,DC=contoso,DC=com</li>
</ul>
</li>
 	<li>Shadow principal shadowing production Domain Admins:
CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso,DC=com
<ul>
 	<li>DNT = 3436 –&gt; DN: CN=ShadowDA.production</li>
 	<li>PDNT = 287–&gt; DN: CN=ShadowDA.production,CN=Shadow Principal Configuration</li>
 	<li>PDNT = 212–&gt; DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services</li>
 	<li>PDNT = 165–&gt; DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration</li>
 	<li>PDNT = 1 –&gt; DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso</li>
 	<li>PDNT = 0 –&gt; DN: CN=ShadowDA.production,CN=Shadow Principal Configuration,CN=Services,CN=Configuration,DC=contoso,DC=com</li>
</ul>
<!--EndFragment--></li>
</ul>
<span style="color: #333333"> </span>

<span style="color: #333333">The member attribute is part of a linked attribute pair in Active Directory. </span>Linked attributes are pairs of attributes in which the system calculates the values of one attribute (the back link) based on the values set on the other attribute (the forward link) throughout the forest.
A back-link value on any object instance consists of the distinguishedNames (DN) of all the objects that have the object's DN set in the corresponding forward link.
The attributes 'member' and 'memberOf' are a pair of linked attributes, where 'member' is the forward link and 'memberOf' is the back link. The link is identified by the linkID values of two attributeSchema definitions:
<ul>
 	<li>The linkID of the forward link is an even, positive, nonzero value.</li>
 	<li>The back link linkID is the forward linkID plus one.</li>
</ul>
<span style="color: #333333">The LinkID for 'member' is 2 and the linkID for 'memberOf' is 3.</span>

<span style="color: #333333">Linked attribute data association is stored in the link table. We do not store the distinguishedName here, but the DNTs.</span>

Structure of the link table – exemplary showing membership of GoldAdmin.production (DNT 3445) in ShadowDA.production (DNT 3436), with naming context DNT (NC_DNT) for the configuration naming context (DNT 165) and an expiration time set to 2017-05-09 06:09:17:
<table width="635" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="104">
<p align="left"><strong>link_DNT</strong></p>
</td>
<td width="94">
<p align="left"><strong>link_base</strong></p>
</td>
<td width="138">
<p align="left"><strong>backlink_DNT</strong></p>
</td>
<td width="93">
<p align="left"><strong>NC_DNT</strong></p>
</td>
<td width="204">
<p align="left"><strong>expiration_time</strong></p>
</td>
</tr>
<tr>
<td width="104">3436</td>
<td width="94">member</td>
<td width="138">3445</td>
<td width="93">165</td>
<td width="204">2017-05-09 06:09:17</td>
</tr>
</tbody>
</table>
Column expiration_time is the place where NTDS is checking for necessary membership revocation and KDC for earliest expiration time of an user's membership for TGT End Time calculation.
Note: If the expiration time is exceeded, the entry in the link_table does not get deleted - it is just treated as invalid and therefore not returned from NTDS when querying group membership.

Just for completeness - NTDS follows the link_DNT –&gt; backlink_DNT chain to calculate direct and nested group membership – example:
<ul>
 	<li>userX (DNT 5610) is member of group2 (DNT 5239)</li>
 	<li>group2 (DNT 5239) is member of group1 (DNT 5112)</li>
</ul>
<blockquote>-&gt; <span style="font-size: medium">userX is member of group2 and group1</span></blockquote>
<table width="678" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="104">
<p align="left"><strong>link_DNT</strong></p>
</td>
<td width="95">
<p align="left"><strong>link_base</strong></p>
</td>
<td width="136">
<p align="left"><strong>backlink_DNT</strong></p>
</td>
<td width="99">
<p align="left"><strong>NC_DNT</strong></p>
</td>
<td width="242">
<p align="left"><strong>expiration_time</strong></p>
</td>
</tr>
<tr>
<td width="104">5112</td>
<td width="95">member</td>
<td width="136">5239</td>
<td width="99">1</td>
<td width="242"></td>
</tr>
<tr>
<td width="104">5239</td>
<td width="95">member</td>
<td width="136">5610</td>
<td width="99">1</td>
<td width="242"></td>
</tr>
</tbody>
</table>
&nbsp;

<strong><u>How do the SIDs in the token come together:</u></strong>

When we use all the above for Domain Admin access to a  Domain Controller in production by a user from the admin forest, the following happens authentication-wise:
<ul>
 	<li>user tries to RDP to resource in production -&gt; resource does not know this user -&gt; needs authentication and authorization</li>
 	<li>authentication request gets redirected to a KDC (Key Distribution Center -&gt; DC) in admin forest</li>
 	<li>KDC builts SID list like described above, including the mirrored SID of the Domain Admins from production</li>
 	<li>DC in production checks group nesting in his database for the SIDs presented from the token issued by the admin forest</li>
 	<li>production DC adds group nesting accordingly to the token
<span style="text-decoration: underline"><strong>!NOTE!</strong> </span>: since security principals from trusted forests can only be nested in domain / builtin local groups of the trusting forest -&gt; group nesting of the mirrored security principal in production will only be applied to the resulting token from groups that are of group type domain local or builtin local.
Nesting in domain global or domain universal groups will not be added to the resulting token.</li>
</ul>
&nbsp;

OK – this were the basics – now to another fun part – the coding.
<span style="color: #333333"><u></u></span>

<span style="color: #333333"><u>PoSh code sample to create the PAM trust between production and admin forest:</u></span>

<span style="color: #333333">This code must be run in the production forest with Domain Admin privileges. It creates the unidirectional forest trust between production and admin forest where production trusts admin forest.</span>

<span style="color: #333333">Mandatory command line argument:</span>
<ul>
 	<li><span style="color: #333333"> –ForestName '<em><span style="color: #0000ff">FDQN of the admin forest'</span></em></span></li>
</ul>
<span style="color: #333333">To create the trust we use the following classes and methods from the System.DirectoryServices.ActiveDirectory (S.DS.AD) namespace:</span>
<ul>
 	<li><span style="color: #333333">class DirectoryContext </span></li>
 	<li><span style="color: #333333">class Forest</span></li>
 	<li><span style="color: #333333">method Forest::CreateTrustRelationship</span></li>
</ul>
<span style="color: #333333">An overview of the available classes and their wrappings in .Net to communicate with Active Directory:</span>

<span style="color: #333333">        <img src="https://msdnshared.blob.core.windows.net/media/TNBlogsFS/prod.evol.blogs.technet.com/CommunityServer.Blogs.Components.WeblogFiles/00/00/00/94/81/SDS-Architecture.jpg" /></span>

Unfortunately the manipulations to trustAttributes flag is not possible per S.DS.AD implementation or direct LDAP modification, we must use some APIs – which is definitely no big fun in PoSh – therefore we call netdom.exe twice to enable sIDHistory and PIM trust for this trust on the trusting side (production).

More details in the code comments.

&nbsp;

[code lang="powershell"]
#region params

param
(
    [Parameter(Position=0,Mandatory=$true)]
    [string]$ForestName    
)

#endregion

#region functions

#region retreive a DC in current forest in closest site
#endregion
function GetDC([string]$forestName)
{
    [string]$ret = [String]::Empty

    $dc = $null

    if ($forestName -ne $null)
    { $dc = Get-ADDomainController -DomainName $forestName -NextClosestSite -Discover }

    else
    { $dc = Get-ADDomainController -NextClosestSite -Discover }

    $ret = $dc.HostName.Value

    return $ret
}

#region create forest trust
# against target forest with given trustdirection and credentials
#endregion
function CreateForestTrust([string]$targetForest, `
                           [System.DirectoryServices.ActiveDirectory.TrustDirection]$direction, `                          
                           [pscredential]$targetUser)
{
    [bool]$created = $false

    # get a DC in current forest
    $dcinfo = GetDC

    try
    {
        # initiate.Net context object from retrieved DC
        [System.DirectoryServices.ActiveDirectory.DirectoryContext]$ctx = `
            New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('DirectoryServer', $dcinfo)         &amp;amp;amp;amp;nbsp;       

        # initiate .Net forest object from context
        [System.DirectoryServices.ActiveDirectory.Forest]$forest = `
            [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($ctx)

        # initiate .Net context object for target forest
        [System.DirectoryServices.ActiveDirectory.DirectoryContext]$tctx = `
            New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', `
										 $targetForest, `
										 $targetUser.UserName, `
										 $targetUser.GetNetworkCredential().Password) 

        # initiate .Net forest object for target forest from context
        [System.DirectoryServices.ActiveDirectory.Forest]$target = `
            [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($tctx) 

        $created = $true
    }

    catch
    {
        Write-Host &quot;Failed to initialize necessary .Net classes : $($_.Exception)&quot;
    }

    if ($created -eq $true)
    {
        $created = $false

        try
        {
            # create foresttrust 
            $forest.CreateTrustRelationship($target, $direction)

            $created = $true

            Write-Host &quot;Created trust between $targetForest and $($forest.Name)&quot;
        }

        catch
        { Write-Host &quot;Failed to create trust between $targetForest and $($forest.Name) : $($_.Exception)&quot; }
    }

    if ($created -eq $true)    
    {
        # Unfortunately we canot edit trustAttributes by LDAP operation -&amp;amp;amp;amp;gt;
        #     we must use LSA APIs -&amp;amp;amp;amp;gt; no big fun in PoSh.
        # Therefore we call netdom.exe here-

        # enable sidhistory
        Write-Host &quot;Setting TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL (sIDHistory) trustAttributes bit&quot;
                
        netdom trust &quot;$($forest.Name)&quot; &quot;/Domain:$targetForest&quot; /EnableSidhistory:Yes
        
        # enable PAM trust
        Write-Host &quot;Setting TRUST_ATTRIBUTE_PIM_TRUST (PAM)&amp;amp;amp;amp;nbsp;trustAttributes bit&quot;
                
        netdom trust &quot;$($forest.Name)&quot; /Domain:$targetForest /EnablePIMTrust:Yes
 
    }    
}

#endregion

#region main

[string]$targetforest = $ForestName

# get target forest admin credentials
[PSCredential]$targetuser = Get-Credential -Message &quot;Provide administrative creds for $targetforest&quot;

# trust direction InBound - we trust the red (bastion) forest
[System.DirectoryServices.ActiveDirectory.TrustDirection]$trustdir = `
	[System.DirectoryServices.ActiveDirectory.TrustDirection]::Outbound

CreateForestTrust $targetforest $trustdir $targetuser 

#endregion
[/code]

&nbsp;

<span style="color: #333333"><u>PoSh code sample to create a shadow principal in the admin forest, shadowing a group (default Domain Admins – RID = 512) from the production forest:</u></span>

<span style="color: #333333">This PoSh must be run in the admin forest with Domain Admin privileges of the forest root domain or Enterprise Admin privileges.</span>

<span style="color: #333333">Mandatory command line argument:</span>
<ul>
 	<li><span style="color: #333333"> –ProdForest '<em><span style="color: #0000ff">FDQN of the production forest'</span></em></span></li>
</ul>
<span style="color: #333333">Optional command line arguments:</span>
<ul>
 	<li><span style="color: #333333"> –ProdGroupRid <span style="color: #0000ff"><em>'RID of the production forest group to shadow' </em></span><span style="color: #333333"> (default = 512 –&gt; Domain Admins)</span></span></li>
 	<li><span style="color: #333333"> –ForceFeature <span style="color: #0000ff"><em>'$true / $false' </em></span><span style="color: #333333"> (default = $true–&gt; calls Enable-ADOptionalFeature 'Privileged Access Management Feature')
</span></span><span style="color: #333333"> </span></li>
</ul>
<span style="color: #333333">In this sample code we mainly use PoSh AD-Cmdlets – only the casting from the Domain Admin's objectSid to a value that can be used to be set in msDs-ShadowPrincipalSid attribute of the shadow principal is done with .Net (System.Security.Principal.SecurityIdentifier::GetBinaryForm).</span>

<span style="color: #333333"><u>Workflow:</u></span>
<ul>
 	<li><span style="color: #333333">get domain SID of production forest and append given group RID (-&gt; the SID to be used in msDs-ShadowPrincipalSid)</span></li>
 	<li>get default msDs-ShadowPrincipalContainer from configuration naming context of admin forest</li>
 	<li>[Optional] enable PAM feature</li>
 	<li>create shadow principal in default msDs-ShadowPrincipalContainer with the SID of the production forest group to shadow</li>
</ul>
More details in the code comments.

&nbsp;

[code lang="powershell"]
#region params

param
(
    # the production forest to contact for it's domain sid
    [Parameter(Mandatory=$true)]
    [string]$ProdForest,

    # the RID of the production forest group to shadow
    [Parameter(Mandatory=$false)]
    [string]$ProdGroupRid = &quot;512&quot;,

    # should we call Enable-ADOptionalFeature Privileged Access Management Feature ?
    [Parameter(Mandatory=$false)]
    [bool]$ForceFeature = $true
)

#endregion

#region functions

#region retrieve default msDs-ShadowprincipalContainer 
# from configuration naming context
#endregion
function GetShadowPrincipalContainer()
{
    [string] $ret = &quot;CN=Shadow Principal Configuration,CN=Services,&quot;

    # get a DC from current forest
    $dc = Get-ADDomainController -NextClosestSite -Discover
    
    # send UDP pipng to port 389 -&amp;amp;amp;amp;gt; rootDSE call
    $rootDSE = Get-ADRootDSE -Server $dc.HostName.Value

    # read configurationNamingContext attribute
    $ret += &quot;$($rootDSE.configurationNamingContext)&quot;

    return $ret
}

#region get SID of the production forest group to shadow
# we ask for production forest domain sid and append given group RID
#endregion
function GetGoldGroupSid([string]$goldForest, [string]$groupRid)
{
    [byte[]] $ret = $null

    # get a DC in production forest
    $dc = Get-ADDomainController -DomainName $goldForest -NextClosestSite -Discover

    # send UDP pipng to port 389 -&amp;amp;amp;amp;gt; rootDSE call
    $rootdse = Get-ADRootDSE -Server $dc.HostName.Value

    # read defaultNamingContext attribute -&amp;amp;amp;amp;gt; domain naming context
    [string] $defnc = $rootdse.defaultNamingContext

    # get domain object - calling for objectSid attribute
    $dom = Get-ADObject $defnc -Server $dc.HostName.Value -Properties objectSid

    # build goup SID
    [string]$sddl = &quot;$($dom.objectSid.Value)-$groupRid&quot;

    # cast to SecurityIdentifier .Net object
    [System.Security.Principal.SecurityIdentifier] $sid = [System.Security.Principal.SecurityIdentifier]$sddl

    Write-Host &quot;Gold SID: $($sid.Value)&quot;

    # resize byte array
    $ret = New-Object byte[] $sid.BinaryLength

    # tranlsate SecurityIdentifier .Net object to byte array
    $waste = $sid.GetBinaryForm($ret, 0)
    
    return $ret
}

#region enable Privileged Access Management Feature (in red forest -&amp;amp;amp;amp;gt; W2K16 AD)
#endregion
function EnableFeature()
{    
    try
    { 
        Enable-ADOptionalFeature &quot;Privileged Access Management Feature&quot; `
				 –Scope ForestOrConfigurationSet `
				 -Target $redforest
    }

    catch { }
}

#endregion

#region main

# Enable-ADOptionalFeature can be skipped by passing -ForceFeature $false
if ($ForceFeature -eq $true)
{
    EnableFeature
}


# get Domain Admins Sid from gold
[byte[]] $bsid  = GetGoldGroupSid $ProdForest $ProdGroupRid


# get path to CN=Shadow Principal Configuration,CN=Services,configNC
[string] $path = GetShadowPrincipalContainer

Write-Host &quot;Shadow Principal Configuration: $path&quot;

# build shadow principal name  for Gold group
[string] $goldshadow = &quot;GoldShadow$ProdGroupRid.$($ProdForest)&quot;

Write-Host &quot;Creating $goldshadow&quot;

try
{
    # create shadow principal
    New-ADObject -Type 'msDs-ShadowPrincipal' `
		 -OtherAttributes @{ 'msDs-ShadowPrincipalSid' = $bsid } `
		 -Name $goldshadow `
		 -Path $path

    Write-Host &quot;Created $goldshadow&quot;
}

catch
{
    Write-Host &quot;Failed to create $goldshadow ($($_.Exception.Message))&quot;
}

#endregion
[/code]

&nbsp;

<span style="color: #333333"><u>PoSh code sample to (temporarely) nest an account from the admin forest into a shadow principal object of the admin forest:</u></span>

<span style="color: #333333">This script must be run in the admin forest with Domain Admin privileges of the forest root domain or Enterprise Admin privileges.</span>

<span style="color: #333333">Mandatory command line arguments:</span>
<ul>
 	<li><span style="color: #333333">–GoldCardUser '<em><span style="color: #0000ff">the cn of the user to be added as member to the shadow group</span><span style="color: #0000ff">'</span></em></span></li>
 	<li>–ShadowGroup '<em><span style="color: #0000ff">the cn of the shadow group</span><span style="color: #0000ff">'</span></em></li>
</ul>
<span style="color: #333333">Optional command line arguments:</span>
<ul>
 	<li><span style="color: #333333"> –TTLMinutes <span style="color: #0000ff"><em>'minutes the membership should be valid' </em></span><span style="color: #333333"> (default = 30)</span></span></li>
</ul>
<span style="color: #333333"><strong>Note:</strong> The given TTL must not be less than the max time skew for Kerberos authentication (default = 5 mins) or 0.</span>

<span style="color: #333333"><u>Workflow:</u></span>
<ul>
 	<li>get default msDs-ShadowPrincipalContainer from configuration naming context of admin forest</li>
 	<li>find shadow principal in default msDs-ShadowPrincipalContainer</li>
 	<li>build add request string depending on whether we passed a TTL time span or not</li>
 	<li>send add modification request for member attribute</li>
</ul>
More details in the code comments.

&nbsp;

[code lang="powershell"]
#region params

param
(
    # the cn of the user to be added as member to the shadow group
    [Parameter(Mandatory=$true)]
    [string]$GoldCardUser,

    # the cn of the shadow group
    [Parameter(Mandatory=$true)]
    [string]$ShadowGroup,

    # how many minutes should the membership be valid?
    # NOTE - if the remaining TTL is less than the max time skew for Kerberos auth 
    # (default 5 mins) and not 0 -&amp;amp;amp;amp;gt; no authentication will be possible
    [Parameter(Mandatory=$false)]
    [int]$TTLMinutes = 30
)

#endregion

#region functions 

#region retrieve default msDs-ShadowprincipalContainer 
# from configuration naming context
#endregion
function GetShadowPrincipalContainer()
{
    [string] $ret = &quot;CN=Shadow Principal Configuration,CN=Services,&quot;

    # get a DC from current forest
    $dc = Get-ADDomainController -NextClosestSite -Discover
    
    # send UDP pipng to port 389 -&amp;amp;amp;amp;gt; rootDSE call
    $rootDSE = Get-ADRootDSE -Server $dc.HostName.Value

    # read configurationNamingContext attribute
    $ret += &quot;$($rootDSE.configurationNamingContext)&quot;

    return $ret
}

#region get string to be used in LDAP add modification for member
#endregion
function BuildAddRequest([string]$goldCardCN, [int]$ttlMins)
{
    [string] $ret = [String]::Empty

    # get goldcard admin for gold forest
    $goldmember = Get-ADUser -Filter { CN -eq $goldCardCN }

    # we have a TTL ?
    if ($ttlMins -ne 0)
    {
        $ttl = New-TimeSpan -Minutes $ttlMins

        # syntax must be &amp;amp;amp;lt;TTL=TimeToLife in seconds,path to member&amp;amp;amp;gt;
        $ret = &quot;&amp;amp;amp;lt;TTL=$($ttl.TotalSeconds),$($goldmember.DistinguishedName)&amp;amp;amp;gt;&quot;
    }

    else
    {
        # no TTL -&amp;amp;amp;amp;gt; standard add modification call
        $ret = $goldmember.DistinguishedName
    }


    return $ret
}

#endregion

#region main

# get path to CN=Shadow Principal Configuration,CN=Services,configNC
[string] $path = GetShadowPrincipalContainer

# find gold shadow group in CN=Shadow Principal Configuration,CN=Services,configNC
$goldshadow = Get-ADObject -Filter { CN -eq $ShadowGroup } -SearchBase $path -SearchScope Subtree


# build value to ad to member attribute
[string] $addreq = BuildAddRequest $GoldCardUser $TTLMinutes


# add membership for goldcard admin with TTL
Set-ADObject $goldshadow -Add @{ member = $addreq }

# can only be used for group objects in a domain naming context:
#Add-ADGroupMember 'testttl' -Members $goldmember -MemberTimeToLive (New-TimeSpan -Minutes $TTLMinutes)
[/code]

&nbsp;

Update (10/31/2017):

Some of us had an issue where production forest has child domains (not an uncommon scenario, isn't it?).

Even though we added msDs-ShadowPricipals shadowing the Domain Admin SIDs of the child domains -&gt; no logon / authentication with these SIDs to the child DCs was possible. Enterpise Admin SID shadowing didn't work either.
Thus it looked like we only can use this security feature in the production forest root domain -&gt; not OK.

Mitigation:
<ul>
 	<li>logon to a DC in each child domain (if you have a multi-leveled forest tree, start from the top)</li>
 	<li>open 'Active Directory Domain and Trusts' management console</li>
 	<li>find incoming and outgoing parent&lt;-&gt;child trusts with the parent -&gt; open properties -&gt; click &lt;Validate&gt;</li>
 	<li>wait for replication to all DCs in the domain</li>
 	<li>restart all DCs</li>
 	<li>logon to a production forest root DC with an account from admin forest carrying either
<ul>
 	<li>prodroot\Domain Admins and child\Domain Admins SIDs from msDs-ShadowPrincipal assignment</li>
 	<li>prodroot\Entrprise Admins SID from msDs-ShadowPrincipal assignment</li>
</ul>
</li>
 	<li>RDP to a child DC with this account &gt;- should be successfull</li>
</ul>
Now you can RDP or authenticate with the shadowed SIDs from an admin forest machine directly to the child DCs.

&nbsp;

Update (9/29/2018)

Added section <span><u>How do the SIDs in the token come together:</u></span>

&nbsp;

All the best and have fun PAMing.

Michael

PFE | Have keyboard. Will travel.

&nbsp;

&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>User provisioning and Password_Not_Required set - why?</title>
      <link>https://docs.microsoft.com/archive/blogs/fieldcoding/user-provisioning-and-password_not_required-set-why</link>
      <pubDate>Fri, 17 Jul 2015 05:00:19 GMT</pubDate>
      <dc:creator><![CDATA[Michael Frommhold MSFT]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/fieldcoding/2015/06/12/user-provisioning-and-password_not_required-set-why/</guid>
      <description><![CDATA[&nbsp;
Today's topic: 
User provisioning and Password_Not_Required set - why?


In large...]]></description>
      <content:encoded><![CDATA[&nbsp;
<p style="margin: 0in 0in 10pt"><span style="font-size: medium;font-family: calibri">Today's topic: </span></p>
<p align="center" style="text-align: center;margin: 0in 0in 10pt"><b><span style="font-size: 12pt;font-family: 'Cambria',serif;color: #4f6228;line-height: 115%"><span style="font-size: medium">User provisioning and Password_Not_Required set - why?
</span></span></b></p>
<span style="font-size: medium;font-family: calibri">
<span style="color: #333333">In large environments, it's definitely a clever idea having some Identity Management in place that - amongst other things - takes care for user provisioning.</span></span>

<span style="color: #333333"><span style="font-size: medium;font-family: calibri">Many of the solutions out there - custom built or ready solutions - do utilize the Active Directory Service Interface (ADSI) or one of its wrappers (like System.DirectoryServices) to create user accounts.
</span><span style="font-size: medium;font-family: calibri">As a result we very often see the provisioned user accounts having the flag ADS_UF_PASSWD_NOTREQD</span> <span style="font-size: medium;font-family: calibri">(0x20) in userAccountControl attribute set.</span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">This is definitely a security risk since a domain admin can set a blank pwd on such an account which then can be used for logon.</span></span>

&nbsp;

<span style="color: #333333"><span style="text-decoration: underline"><span style="font-size: medium;font-family: calibri">Question:</span></span><span style="font-size: medium;font-family: calibri"> Why did this happen?</span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">A common thinking error here is that you must first create the user and can only then set the password after the user is already in AD.</span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">Samples in the internet suggesting to create the user and then use the ADSI method SetPassword() after having the user created – like in the following code sample:</span></span>
<div id="codeSnippetWrapper" style="font-size: 8pt;overflow: auto;cursor: text;font-family: 'Courier New', courier, monospace;width: 97.5%;direction: ltr;text-align: left;margin: 20px 0px 10px;line-height: 12pt;background-color: #f4f4f4;border: silver 1px solid;padding: 4px">
<div id="codeSnippet" style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    DirectoryEntry user = <span style="color: #0000ff">null</span>;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">try</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #008000">// create user on dedicated dc to make sure we find the user </span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #008000">// for post processing (DCLocator vs. replication)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        DirectoryEntry ou = <span style="color: #0000ff">new</span> DirectoryEntry(<span style="color: #006080">"LDAP://"</span> + dcName + <span style="color: #006080">"/"</span> + ouPath,</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">                                                <span style="color: #0000ff">null</span>, <span style="color: #0000ff">null</span>, AuthenticationTypes.Sealing);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #008000">// Add user to OU (note - we are still in the RAM - nothing happens in AD here)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        user = ou.Children.Add(<span style="color: #006080">"CN=ADSI Test"</span>, <span style="color: #006080">"user"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #008000">// set logon name (sAMAccountName)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        user.Properties[<span style="color: #006080">"sAMAccountName"</span>].Value = <span style="color: #006080">"adsitest"</span>;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #008000">// set userPrincipalName</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        user.Properties[<span style="color: #006080">"userPrincipalName"</span>].Value = <span style="color: #006080">"adsitest@mfp-labs.labsetup.org"</span>;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #008000">// write from RAM to AD</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        user.CommitChanges();</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        Console.WriteLine(<span style="color: #006080">"Success adding user"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">catch</span> (Exception ex)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    { Console.WriteLine(<span style="color: #006080">"Adding user: {0} ({1})"</span>, ex.Message, ex.GetType()); }</pre>
<!--CRLF-->

</div>
</div>
<span style="font-size: medium;font-family: calibri">
<span style="color: #333333">This results in an account with
</span></span><span style="color: #333333;font-family: Calibri;font-size: medium">- userAccountControl = 0x222 (ADS_UF_ACCOUNTDISABLE  | ADS_UF_PASSWD_NOTREQD | ADS_UF_NORMAL_ACCOUNT</span><span style="font-size: medium"><span style="color: #333333"><span style="font-family: calibri">)
</span><span style="font-family: Calibri">- pwdLastSet = 0 (never -&gt; user must change password at next logon)</span></span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">Now we must set the initial password by invoking the method SetPassword of the underlying IADs object (can be inspected in user.NativeObject).
SetPassword wraps the following tasks:</span></span>
<ul>
 	<li><span style="color: #333333;font-family: Calibri;font-size: small">Bind to the Domain Controller we used in the creation task with AuthenticationType.Sealing set.
Note – we need Kerberos enabled on the connection to be able to set the password.</span></li>
 	<li><span style="color: #333333;font-family: Calibri;font-size: small">Perform a replace modification request on the attribute <i>unicodePwd</i> against the path of the calling IADs object (NativeObject.AdsPath)</span></li>
</ul>
<span style="font-size: medium;font-family: calibri"><span style="color: #333333">You see why we need to pre-create the user before we can use the SetPassword method?</span></span>
<div style="font-size: 8pt;font-family: consolas;background: white;color: black">
<div id="codeSnippetWrapper" style="font-size: 8pt;overflow: auto;cursor: text;font-family: 'Courier New', courier, monospace;width: 97.5%;direction: ltr;text-align: left;margin: 20px 0px 10px;line-height: 12pt;background-color: #f4f4f4;border: silver 1px solid;padding: 4px">
<div id="codeSnippet" style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">try</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #008000">// this is a separate call into AD - no commit required</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #008000">// therefore we need the account to be present already</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        user.Invoke(<span style="color: #006080">"SetPassword"</span>, <span style="color: #006080">"P@ssWord"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        Console.WriteLine(<span style="color: #006080">"Success setting pwd"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    <span style="color: #0000ff">catch</span> (Exception ex)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    { Console.WriteLine(<span style="color: #006080">"Setting pwd: {0} ({1})"</span>, ex.Message, ex.GetType()); }</pre>
<!--CRLF-->

</div>
</div>
<pre style="margin: 0px"><span style="font-size: medium;font-family: calibri"> </span></pre>
<span style="color: #333333"><span style="font-size: medium;font-family: calibri">Now we have
</span><span style="font-family: Calibri;font-size: medium">- userAccountControl = 0x222 (ADS_UF_ACCOUNTDISABLE  | ADS_UF_PASSWD_NOTREQD | ADS_UF_NORMAL_ACCOUNT</span></span><span style="font-size: medium"><span style="color: #333333"><span style="font-family: calibri">)
</span><span style="font-family: Calibri">- pwdLastSet = time of SetPassword() (user <strong>not</strong> must change password at next logon)</span></span></span>

</div>
<span style="color: #333333"><span style="font-size: medium"><span style="font-family: calibri">Saying – any domain admin may still reset the user’s password to a blank password.</span></span></span>

<span style="font-family: calibri;font-size: medium"><span style="color: #333333"><span style="font-family: calibri"><span style="font-size: medium"><span style="text-decoration: underline">Answer:</span> Because the following task very often just vanished in oblivion we stay with ADS_UF_PASSWD_NOTREQD flag set.</span></span></span></span>

<span style="color: #333333"><span style="font-size: medium;font-family: calibri">We <span style="text-decoration: line-through">should</span> must adjust userAccountControl and pwdLastSet to unset ADS_UF_PASSWD_NOTREQD</span><span style="font-family: Calibri"> <span style="font-size: medium;font-family: calibri">flag and force the user to change password at next logon:</span></span></span>
<div id="codeSnippetWrapper" style="font-size: 8pt;overflow: auto;cursor: text;font-family: 'Courier New', courier, monospace;width: 97.5%;direction: ltr;text-align: left;margin: 20px 0px 10px;line-height: 12pt;background-color: #f4f4f4;border: silver 1px solid;padding: 4px">
<div id="codeSnippet" style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">try</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #008000">// make account enabled / without pwd_not_req flag</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        user.Properties[<span style="color: #006080">"userAccountControl"</span>].Value = 512;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #008000">// force user must change pwd at next logon</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #008000">// we must pass a lon value -&gt; cast 0 to long</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        user.Properties[<span style="color: #006080">"pwdLastSet"</span>].Value = (<span style="color: #0000ff">long</span>)0;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #008000">// write from RAM to AD</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        user.CommitChanges();</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        Console.WriteLine(<span style="color: #006080">"Success setting userAccountControl + pwdLastSet"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    <span style="color: #0000ff">catch</span> (Exception ex)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    { Console.WriteLine(<span style="color: #006080">"Setting userAccountControl + pwdLastSet: {0} ({1})"</span>, ex.Message,</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">                                                                              ex.GetType()); }</pre>
<!--CRLF-->

</div>
</div>
<pre style="margin: 0px"><!--EndFragment--></pre>
<pre style="margin: 0px"><!--EndFragment--></pre>
<!--EndFragment-->

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">Finally we have a provisioned account with acceptable settings.
</span></span>

<span style="font-family: calibri"><span style="font-size: medium"><span style="color: #333333"><span style="text-decoration: underline">Question:</span> Why is this a thinking error?</span></span></span>
<p style="margin: 0in 0in 8pt"><span style="font-family: calibri"><span style="font-size: medium"><span style="color: #333333"><span style="text-decoration: underline">Answer:</span> You guessed it - because we can set the password while creating a user account.</span></span></span></p>
<span style="font-size: medium;font-family: calibri"><span style="color: #333333"><span style="font-family: calibri"><span style="font-size: medium"><span style="text-decoration: underline">Question:</span> </span></span>But how – we have been reading above that you need to pre-create the user account before you can set the password?</span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333"><span style="font-family: calibri"><span style="font-size: medium"><span style="text-decoration: underline">Answer:</span> </span></span>Just don’t use the method SetPassword in ADSI but perform the wrapped processing in your creation task. </span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">Saying - perform the replace modification on <em>unicodePwd</em> attribute while creating the user  in RAM before writing into AD:</span></span>
<div style="font-size: 8pt;font-family: consolas;background: white;color: black">
<pre style="margin: 0px"><span style="font-size: medium;font-family: calibri"><!--EndFragment--></span></pre>
<div id="codeSnippetWrapper" style="font-size: 8pt;overflow: auto;cursor: text;font-family: 'Courier New', courier, monospace;width: 97.5%;direction: ltr;text-align: left;margin: 20px 0px 10px;line-height: 12pt;background-color: #f4f4f4;border: silver 1px solid;padding: 4px">
<div id="codeSnippet" style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> SDS(<span style="color: #0000ff">string</span> dcName, <span style="color: #0000ff">string</span> ouPath)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        DirectoryEntry user = <span style="color: #0000ff">null</span>;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #0000ff">try</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// create user on dedicated dc to make sure we find the user </span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// for post processing (DCLocator vs. replication) and</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// set AuthenticationTypes.Sealing to enable pwd update</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            DirectoryEntry ou = <span style="color: #0000ff">new</span> DirectoryEntry(<span style="color: #006080">"LDAP://"</span> + dcName + <span style="color: #006080">"/"</span> + ouPath,</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">                                                    <span style="color: #0000ff">null</span>, <span style="color: #0000ff">null</span>, AuthenticationTypes.Sealing);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// Add user to OU (note - we are still in the RAM - nothing happens in AD here)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            user = ou.Children.Add(<span style="color: #006080">"CN=ADSI Test"</span>, <span style="color: #006080">"user"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// set logon name (sAMAccountName)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            user.Properties[<span style="color: #006080">"sAMAccountName"</span>].Value = <span style="color: #006080">"adsitest"</span>;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// set userPrincipalName</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            user.Properties[<span style="color: #006080">"userPrincipalName"</span>].Value = <span style="color: #006080">"adsitest@mfp-labs.labsetup.org"</span>;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// pass pwd with account creation call</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            user.Properties[<span style="color: #006080">"unicodePwd"</span>].Value = BuildBytePWD(<span style="color: #006080">"P@ssW0rd"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// make account enabled / without pwd_not_req flag</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            user.Properties[<span style="color: #006080">"userAccountControl"</span>].Value = 512;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// write from RAM to AD</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            user.CommitChanges();</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            Console.WriteLine(<span style="color: #006080">"Success adding user"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #0000ff">catch</span> (Exception ex)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        { Console.WriteLine(<span style="color: #006080">"Adding user: {0} ({1})"</span>, ex.Message, ex.GetType()); }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">byte</span>[] BuildBytePWD(<span style="color: #0000ff">string</span> pwd)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #0000ff">return</span> (Encoding.Unicode.GetBytes(String.Format(<span style="color: #006080">"\"{0}\""</span>, pwd)));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->
</div>

&nbsp;
</div>

&nbsp;

<pre style="margin: 0px"><span style="font-size: medium;font-family: calibri"> </span></pre>
</div>
<p style="margin: 0in 0in 8pt"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">Alternatively we say good bye to ADSI and use the cool LDAP API wrapper System.DirectoryServices.Protocols:</span>
</span></p>

<div id="codeSnippetWrapper" style="font-size: 8pt;overflow: auto;cursor: text;font-family: 'Courier New', courier, monospace;width: 97.5%;direction: ltr;text-align: left;margin: 20px 0px 10px;line-height: 12pt;background-color: #f4f4f4;border: silver 1px solid;padding: 4px">
<div id="codeSnippet" style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span> SDSP(<span style="color: #0000ff">string</span> dcName, <span style="color: #0000ff">string</span> ouPath)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #008000">// open LDAP connection to DC on port 389 (-&gt; not GC =&gt; writable AD)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        LdapDirectoryIdentifier ldap_id = <span style="color: #0000ff">new</span> LdapDirectoryIdentifier(dcName, 389, <span style="color: #0000ff">true</span>, <span style="color: #0000ff">false</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #0000ff">using</span> (LdapConnection ldap_con = <span style="color: #0000ff">new</span> LdapConnection(ldap_id))</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// no kerberos -&gt; no pwd set -&gt; (Error = WILL_NOT_PERFORM)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            ldap_con.SessionOptions.Sealing = <span style="color: #0000ff">true</span>;</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// bind to the DC</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            ldap_con.Bind();</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// list of attributes we want to pass for creation</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            List&lt;DirectoryAttribute&gt; ldap_mod = <span style="color: #0000ff">new</span> List&lt;DirectoryAttribute&gt; { };</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// this has to be passed - otherwise we fail</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            ldap_mod.Add(<span style="color: #0000ff">new</span> DirectoryAttribute(<span style="color: #006080">"objectClass"</span>, <span style="color: #006080">"user"</span>));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// set logon name (sAMAccountName)</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            ldap_mod.Add(<span style="color: #0000ff">new</span> DirectoryAttribute(<span style="color: #006080">"sAMAccountName"</span>, <span style="color: #006080">"adsitest"</span>));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// set userPrincipalName</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            ldap_mod.Add(<span style="color: #0000ff">new</span> DirectoryAttribute(<span style="color: #006080">"userPrincipalName"</span>, <span style="color: #006080">"adsitest@contoso.com"</span>));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// send the password with the creation request</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            ldap_mod.Add(<span style="color: #0000ff">new</span> DirectoryAttribute(<span style="color: #006080">"unicodePwd"</span>, BuildBytePWD(<span style="color: #006080">"P@ssW0rd"</span>)));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// set userAccountControl to  Normal account</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            ldap_mod.Add(<span style="color: #0000ff">new</span> DirectoryAttribute(<span style="color: #006080">"userAccountControl"</span>, (512).ToString()));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #008000">// if we want to enforce pwd change on first logon - </span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// otherwise pwdLastSet is current time</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            ldap_mod.Add(<span style="color: #0000ff">new</span> DirectoryAttribute(<span style="color: #006080">"pwdLastSet"</span>, (0).ToString()));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// build add request</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            AddRequest areq = <span style="color: #0000ff">new</span> AddRequest(<span style="color: #006080">"CN=ADSI test,"</span> + ouPath, ldap_mod.ToArray());</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #0000ff">try</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">                <span style="color: #008000">// send reqeust to DC</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">                AddResponse aret = (AddResponse)ldap_con.SendRequest(areq);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">                Console.WriteLine(<span style="color: #006080">"Success"</span>);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #0000ff">catch</span> (DirectoryOperationException doex)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">                Console.WriteLine(<span style="color: #006080">"{0} ({1})"</span>, doex.Response.ErrorMessage,</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">                                               doex.Response.ResultCode);</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #0000ff">catch</span> (Exception ex)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">                Console.WriteLine(<span style="color: #006080">"{0} ({1})"</span>, ex.Message, ex.GetType());</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    <span style="color: #0000ff">private</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">byte</span>[] BuildBytePWD(<span style="color: #0000ff">string</span> pwd)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #0000ff">return</span> (Encoding.Unicode.GetBytes(String.Format(<span style="color: #006080">"\"{0}\""</span>, pwd)));</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->

</div>
</div>
<p style="margin: 0in 0in 8pt"><span style="font-size: medium"><span style="font-family: calibri">
<span style="color: #333333">OK - cool - now we have code avoiding provisioned user accounts with ADS_UF_PASSWD_NOTREQD</span></span> <span style="font-size: medium;font-family: calibri"><span style="color: #333333">flag set.
</span></span></span></p>
<p style="margin: 0in 0in 8pt"><span style="font-size: medium"><span style="font-family: calibri;font-size: medium"><span style="color: #333333"><span style="text-decoration: underline">NOTE:</span> When we try to set a password that does not meet complexity rules or minimum password length requirement of the domain, we lose a RID from the RID pool for every creation call.
NTDS has to create the user internally to be able to write the given password to unicodePwd attribute -&gt; we need a SID.
If we fail with the password, the pre-created user object gets destroyed. The user object gets not deleted nor recycled - thus you will not be able to find info in the DB about this used RID.
To avoid RID pool exhaustion, try to build proper password creation code and limit the retry loops in your IDM code.
Additionally ask your beloved AD Admins to watch the RID pool on regularly bases.</span></span></span></p>
&nbsp;

<span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="color: #333333"><span style="text-decoration: underline">Question:</span> How do we get rid of this flag on already provisioned user accounts.
</span></span></span>

<span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">We do not know whether a user account has a blank password or not and performing any sort of password hash dump has a big smell of hacking.
</span></span></span>

<span style="color: #333333"><span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="text-decoration: underline">Answer:</span> </span></span><span style="font-size: medium;font-family: calibri">We can rely on an Active Directory built-in security mechanism that takes care that no enabled user account with a blank password and ADS_UF_PASSWD_NOTREQD flag not set exists in AD.</span></span>
<p style="margin: 0in 0in 8pt"><span style="color: #333333"><span style="font-size: medium;font-family: calibri">When unsetting the ADS_UF_PASSWD_NOTREQD flag in userAccountControl on an enabled user account NTDS checks by comparing the user’s current password hash with the hash of a blank password. If we have a match NTDS will not perform the modification and throw an error stating that the current password does not meet complexity rules.
</span><span style="font-size: medium;font-family: calibri">Same will happen when enabling a disabled user account with a blank password and ADS_UF_PASSWD_NOTREQD flag not set.</span></span></p>
<p style="margin: 0in 0in 8pt"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">Note – NTDS does not compare plaintext passwords – it simply doesn’t know these.</span></span></p>
<p style="margin: 0in 0in 8pt"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">Therefore, a possible mitigation would be (thanks to Kevin Bell, PFE as well, for this idea):
</span></span></p>

<ul>
 	<li style="margin-bottom: 8pt;margin-top: 0in;margin-right: 0in"><span style="color: #333333"><span style="font-size: medium;font-family: calibri">find all user accounts (<em>objectClass=user</em>) that are of the type user and not computer (<em>sAMAccountType=805306368</em>) and have the <span style="font-family: calibri">ADS_UF_PASSWD_NOTREQD flag set in userAccountControl (<em>userAccountControl:1.2.840.113556.1.4.804:=32</em>)</span></span> </span></li>
 	<li style="margin-bottom: 8pt;margin-top: 0in;margin-right: 0in"><span style="font-size: medium;font-family: calibri"><span style="font-family: calibri"><span style="color: #333333">walk the retrieved list of user accounts and unset <span style="font-family: calibri">ADS_UF_PASSWD_NOTREQD flag</span>
</span></span></span></li>
 	<li style="margin-bottom: 8pt;margin-top: 0in;margin-right: 0in"><span style="color: #333333"><span style="font-size: medium;font-family: calibri"><span style="font-family: calibri"><span style="font-family: calibri">if we fail with a password complexity error - report this user accounts as users with blank passwords
</span></span></span></span></li>
</ul>
<p style="margin-bottom: 8pt;margin-top: 0in;margin-right: 0in"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">PoSh sample performing the above described tasks:</span></span></p>

<div id="codeSnippetWrapper" style="font-size: 8pt;overflow: auto;cursor: text;font-family: 'Courier New', courier, monospace;width: 97.5%;direction: ltr;text-align: left;margin: 20px 0px 10px;line-height: 12pt;background-color: #f4f4f4;border: silver 1px solid;padding: 4px">
<div id="codeSnippet" style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    $fishyusers = <span style="color: #0000ff">Get-ADUser -LDAPFilter</span> <span style="color: #006080">"(&amp;(objectClass=user)(sAMAccountType=805306368)"</span> +</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">                                         <span style="color: #006080">"(userAccountControl:1.2.840.113556.1.4.804:=32))"</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    <span style="color: #0000ff">foreach</span> ($fish <span style="color: #0000ff">in</span> $fishyusers)</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">    {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #0000ff">try</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #0000ff">Set-ADAccountControl -Identity</span> $fish <span style="color: #0000ff">-PasswordNotRequired</span> $<span style="color: #0000ff">false</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #0000ff">Write-Host</span> <span style="color: #006080">"secured:"</span> $fish.DistinguishedName</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        <span style="color: #0000ff">catch</span> [Microsoft.ActiveDirectory.Management.ADPasswordComplexityException]</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #0000ff">Write-Host</span> <span style="color: #006080">"blank pwd for:"</span> $fish.DistinguishedName</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">            <span style="color: #008000">// report into a log file?</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px"></pre>

<!--CRLF-->

<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">        <span style="color: #0000ff">catch</span></pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        {</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">            <span style="color: #0000ff">Write-Host</span> $_.Exception.ToString()</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: white;border-style: none;padding: 0px">        }</pre>
<!--CRLF-->
<pre style="font-size: 8pt;overflow: visible;font-family: 'Courier New', courier, monospace;width: 100%;color: black;direction: ltr;text-align: left;margin: 0em;line-height: 12pt;background-color: #f4f4f4;border-style: none;padding: 0px">    }</pre>
<!--CRLF-->

</div>
</div>
<span style="font-size: medium"><span style="font-size: medium;font-family: calibri"> </span></span>

<span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">What are the risks to do this in a production environment (?):</span></span></span>
<ul>
 	<li><span style="color: #333333"><span style="font-size: medium"><span style="font-size: medium;font-family: calibri">user accounts on which we could unset the flag successfully have no blank passwords and can go on working - but are more secure</span></span> </span></li>
 	<li><span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">user accounts on which we failed with password complexity error did not get modified - thus they can still work - but can be contacted and requested to set a complex password.
</span></span></span></li>
 	<li><span style="color: #333333"><span style="font-size: medium"><span style="font-size: medium;font-family: calibri">depending on the amount of user accounts that have been modified we may have increased replication traffic after the modifications.
-&gt; It could be a clever idea to do this out of work hours.</span></span></span></li>
</ul>
<span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="color: #333333"> </span></span></span>

<span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">Hope you had some fun reading and wish fun with testing.</span></span></span>

<span style="font-size: medium"><span style="font-size: medium;font-family: calibri"><span style="color: #333333">History:
4/13/2016: added NOTE about possible excessive RID pool consumption.
</span></span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">All the best</span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">Michael</span></span>

<span style="font-size: medium;font-family: calibri"><span style="color: #333333">PFE | Have keyboard. Will travel.</span></span>]]></content:encoded>
    </item>
  </channel>
</rss>