Jorge's Quest For Knowledge!

About Windows Server, ADDS, ADFS, Azure AD, FIM/MIM & AADSync (Just Like An Addiction, The More You Have, The More You Want To Have!)

(2016-05-25) LithNet FIM Sync PowerShell CMDlets

Posted by Jorge on 2016-05-25


Ryan Newington  (Twitter and Blog), yet again wrote and created an impressive PowerShell module to manage the FIM/MIM Sync Engine. Many stuff to manage the FIM/MIM Sync Engine that was not yet possible through PowerShell, is now possible!!!

Lithnet FIM/MIM Synchronization Service PowerShell Module

DISCLAIMER

WARNING: USE THIS TOOL AT YOUR OWN RISK

This tool is provided for testing and diagnostic purposes and is intended for use in development and test environments. Any problems that arise from the use of the tool are not supported by the developers or by Microsoft.

The PowerShell module exposes functionality using a combination of

Supported WMI interfaces
Wrapping existing PowerShell modules
Wrapping existing executables
Libraries that the synchronization client UI uses to interface with the sync engine itself. These libraries are undocumented APIs.

The module does NOT interface with the sync engine database in any way.

It does not provide any mechanism to alter the internal configuration of the sync engine, unless an executable or documented API is available for that.

The developers make no warranties as to the suitability of these tools for use in your environment, nor will we be liable for any financial or other damages arising from the use of these tools.

image

Figure 1: The List Of PowerShell CMDlets In The Lithnet PowerShell Module For FIM/MIM Sync

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Forefront Identity Manager (FIM) Sync, PowerShell, Tools | Leave a Comment »

(2016-05-20) Azure AD Connect Configuration Documenter

Posted by Jorge on 2016-05-20


Have you ever wanted to document your Azure AD Connect Configuration? Yes? Well wait no longer! This is really a very interesting tool!

The AAD Connect configuration documenter is a tool to generate HTML based documentation of an Azure AD Connect installation. Currently, the documentation is only limited to the Azure AD Connect sync configuration. Another interesting part is that you can click through the configuration!

The goal of this project is to:

  • To enable quick understanding of the synchronization configuration and "how it happens"!
  • To build confidence in getting things right when making changes to the default configuration!!
  • To know what was changed when you applied a new build / configuration of Azure AD Connect or added/updated custom sync rules!!!

Prerequisites:

  1. .NET Framework 4.5 to be able to run the tool
  2. A fair understanding of MIIS 2003 / ILM 2007 / FIM 2010 / MIM 2016 sync engine technical concepts to be able to understand the report.

I (Jorge) have provide a sample report so that you can see how it looks like. Click here for the sample report.

How to use the tool:

  • Download the latest release from the releases tab under the Code tab tab, UNBLOCK the downloaded zip file and extract the zip file to an empty local folder on a machine which has .NET Framework 4.5 installed.
    • This will extract the Documenter application binaries along with the sample data files for "Contoso".
    • Make sure that the tool runs by double-clicking on the cmd file AzureADConnectSyncDocumenter.cmd.
  • Export the Server Configuration of your pilot / test Azure AD Connect sync server by running Get-ADSyncServerConfiguration cmdlet defined in ADSync module shipped with Azure AD Connect.

Import-Module ADSync 
Get-ADSyncServerConfiguration -Path "<CompletePathToOutputFolder>"

  • Copy the configuration export files produced in the previous step to a folder under the "Data" directory of the Documenter tool.
    • e.g. the "Pilot" configuration files for the customer "Contoso" are provided as a sample under the "Data\Contoso\Pilot" folder.
  • If you want to document the changes from a specific baseline, export the server configuration of your baseline / production Azure AD Connect server and copy the output to a folder under the Documenter "Data" directory.
    • e.g. the "Production" configuration files for the customer "Contoso" are provided as a sample under the "Data\Contoso\Production" folder.
  • Edit AzureADConnectSyncDocumenter.cmd for the values of "Pilot" and "Production" directories.
  • If you don’t have a baseline / production config, specify the same path as the "Pilot" config.
  • Run the updated batch file. Upon successful execution, the generated report will be found in the Documenter "Report" folder.

I (Jorge) have provide a sample report so that you can see how it looks like. Click here for the sample report.

You can get the Azure AD Connect Documenter from here

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Azure AD Connect, Documenter, Windows Azure Active Directory | Leave a Comment »

(2016-05-16) Azure AD Connect Health Throws An Error During Azure AD Connect Install

Posted by Jorge on 2016-05-16


During the installation of Azure AD connect you might experience and see the following during the installation/configuration of the “Azure AD Connect Health Agent for Sync”

Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object

image

Figure 1: Azure AD Connect Health For Sync Crashing

The installation of “Azure AD Connect Health Agent for Sync” crashed, it tried to find a solution, and in the end I was allowed to close the program. This happened about 3 times or so. Then the regular azure AD Connect installation continued. The Sync Engine is working perfectly afterwards without any issues.

It may appear the “Azure AD Connect Health Agent for Sync” installation has failed. Au contraire! The installation of “Azure AD Connect Health Agent for Sync” succeeded, but its registration is actually failing!

With Azure AD Connect two components require internet access. A third, the Azure AD PowerShell CMDlets if installed in addition manually, also requires internet access.

If you are using direct connections you only need to open up the correct firewall ports to specific URLs/IP addresses.

If you are using a proxy server to connect through, the proxy server must be configured to allow all three components to target the proxy server for internet access to specific URLs/IP addresses

For authentication and access, both the Azure AD PowerShell CMDlets and Azure AD Connect Sync Engine requires access to the following URLs:
(Details –>
Office 365 URLs and IP address ranges)

  • *.microsoftonline.com (port 443)
  • *.windows.net(port 443)
  • secure.aadcdn.microsoftonline-p.com (port 443)
  • mscrl.microsoft.com (port 80)

For authentication and access, the Azure AD Connect Health Agent requires access to the following URLs:
(Details –>
Office 365 URLs and IP address ranges and Azure AD Connect Health Agent Installation)

  • *.blob.core.windows.net (port 443)
  • *.queue.core.windows.net (port 443)
  • *.table.core.windows.net(port 443)
  • *.servicebus.windows.net (port: 5671 recommended, if 5671 is blocked, the agent falls back to 443)
  • *.adhybridhealth.azure.com(port 443)
  • policykeyservice.dc.ad.msft.net (port 443)
  • login.windows.net (port 443)
  • login.microsoftonline.com (port 443)
  • secure.aadcdn.microsoftonline-p.com (port 443)
  • management.azure.com (port 443)

All the three components have their way of configuring proxy settings. However, you can only configure two of those components before the installations. The third one also requires internet access prior to the installation, but you can only configure the proxy settings after the installation of the Azure AD Connect Health Agent. Kinda of a chicken and the egg scenario. This is the reason why the above error occurs.

Prior to the installation of the Azure AD PowerShell CMDlets configure the proxy as follows:

NETSH.EXE WINHTTP SHOW PROXY

NETSH.EXE WINHTTP SET PROXY PROXY-SERVER="<PROXYSERVER>:<PORT>" BYPASS-LIST="<wildcard domain 1>;<wildcard domain 1>;<local>"

NETSH.EXE WINHTTP SHOW PROXY

REMARK: Because you might use PowerShell to connect to internal resources, make sure to configure all top level domains in your internal network in the bypass-list. For every internal domain configure it as shown between the double quotes “*.domain.com”. If these proxy settings are configure PowerShell will use them. if you do not configure the internal domains in the bypass list you might experience connection issues as explained in this blog post.

Prior to the installation of the Azure AD Connect configure the proxy as follows:

Edit the file “C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config” and configure the following section at the end of the file, just before the </configuration> line

    <system.net>

        <defaultProxy>

            <proxy

                usesystemdefault="true"

                proxyaddress="="<PROXYSERVER>:<PORT>"

                bypassonlocal="true"

            />

            <bypasslist>

                <add address="<regular expression for internal top level domain Azure AD Connect is connecting to>" />

            </bypasslist>

        </defaultProxy>

    </system.net>

REMARK: For every internal domain (*.domain.com) Azure AD Connect is connecting to configure it as shown between the double quotes “.*\.domain\.com$”.

Now start the Azure AD Connect installation, configure what needs to be configured. At some point in time, if internet access needs to go through a proxy and it is a tightly controlled proxy, you most likely will experience what is shown in figure 1. The Azure AD Connect Health Agent installation will try crash three times in total. After the installation of Azure AD connect successfully finishes, you need to manually register the Azure AD Connect Health Agent.

If you execute the following commands for the Azure AD Connect Health Agent

$azureUserName="<USERNAME>"

$azurePassword='<PASSWORD>’

$azureSecurePassword = ConvertTo-SecureString $azurePassword -AsPlainText -Force

$azureCreds = New-Object System.Management.Automation.PSCredential $azureUserName, $azureSecurePassword

Register-AzureADConnectHealthSyncAgent -Credential $azureCreds

…without first configuring the proxy settings for Azure AD Connect Health, you will see:

Click [Close Program]

image

Figure 2: First Crashing Occurrence After Registering Azure AD Connect Health Agent Manually

Click [Close Program]

image

Figure 3: Second Crashing Occurrence After Registering Azure AD Connect Health Agent Manually

Click [Close Program]

image

Figure 4: Third Crashing Occurrence After Registering Azure AD Connect Health Agent Manually

Click [Close Program]

image

Figure 5: Notification The Azure AD Connect Health Agent Registration Failed

In the Application Event Log you will something similar to the following 3 times:

Application: Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe
Framework Version: v4.0.30319
Description: The process was terminated due to an unhandled exception.
Exception Info: System.NullReferenceException
Stack:
   at Microsoft.Identity.Health.Common.ETWTraceListener.Write(System.Object)
   at System.Diagnostics.TraceSource.TraceEvent(System.Diagnostics.TraceEventType, Int32, System.String)
   at Microsoft.Online.Reporting.MonitoringAgent.AgentTrace.LogEvent(Int32, System.Diagnostics.EventLogEntryType, System.String, System.String, System.Object[])
   at Microsoft.Online.Reporting.MonitoringAgent.AgentTrace.LogError(Int32, System.String, System.String, System.Object[])
   at Microsoft.Online.Reporting.MonitoringAgent.Startup.Program.Main(System.String[])

Faulting application name: Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe, version: 1.1.28.2, time stamp: 0x55e8976e
Faulting module name: unknown, version: 0.0.0.0, time stamp: 0x00000000
Exception code: 0xc0000005
Fault offset: 0x00007ffcbd764169
Faulting process id: 0x1618
Faulting application start time: 0x01d1aa7df40424dd
Faulting application path: C:\Program Files\Microsoft Azure AD Connect Health Sync Agent\Monitor\Microsoft.Identity.Health.AadSync.MonitoringAgent.Startup.exe
Faulting module path: unknown
Report Id: 36758953-1671-11e6-80d4-001dd8b72864
Faulting package full name:
Faulting package-relative application ID:

Fault bucket 129024325829, type 5
Event Name: CLR20r3
Response: Not available
Cab Id: 0

Problem signature:
P1: 4IQPNWPJFYKLTMQR4N2HHQMZN041TJWC
P2: 1.1.28.2
P3: 55e8976e
P4: Microsoft.Identity.AadConnect.Health.AadSync.Utils
P5: 2.6.107.0
P6: 56b4f9ab
P7: 163
P8: 1e
P9: System.NullReferenceException
P10:

Attached files:
C:\Users\XXXX\AppData\Local\Temp\WERC02B.tmp.WERInternalMetadata.xml

These files may be available here:
C:\Users\XXXX\AppData\Local\Microsoft\Windows\WER\ReportArchive\AppCrash_4IQPNWPJFYKLTMQR_468cf53638b6fdf68ca8c15c4fe379c96dbec3_eb61a0cf_5903cc60

Analysis symbol:
Rechecking for solution: 0
Report Id: 36758953-1671-11e6-80d4-001dd8b72864
Report Status: 0
Hashed bucket: b10faeb2a429840ab102a724bbd62245

Now, the correct way to do this right for the Azure AD Connect Health Agent is by executing the following commands…

Get-AzureAdConnectHealthProxySettings

If you used NETSH earlier to configure WinHTTP proxy settings, now use –> Set-AzureAdConnectHealthProxySettings -ImportFromWinHttp

If you DID NOT used NETSH earlier to configure WinHTTP proxy settings, now use –> Set-AzureAdConnectHealthProxySettings -HttpsProxyAddress <PROXYSERVER>:<PORT>

Get-AzureAdConnectHealthProxySettings

$azureUserName="<USERNAME>"

$azurePassword='<PASSWORD>’

$azureSecurePassword = ConvertTo-SecureString $azurePassword -AsPlainText -Force

$azureCreds = New-Object System.Management.Automation.PSCredential $azureUserName, $azureSecurePassword

Register-AzureADConnectHealthSyncAgent -Credential $azureCreds

You should now see something similar to:

image

Figure 6: Notification The Azure AD Connect Health Agent Registration Was Successful

You should be good now! Smile

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Azure AD Connect, Azure AD Connect Health, Windows Azure Active Directory | Leave a Comment »

(2016-05-14) Azure AD Connect v1.1.180.0 Has Been Released

Posted by Jorge on 2016-05-14


Integrating your on-premises directories with Azure AD makes your users more productive by providing a common identity for accessing both cloud and on-premises resources. With this integration users and organizations can take advantage of the following:

  • Organizations can provide users with a common hybrid identity across on-premises or cloud-based services leveraging Windows Server Active Directory and then connecting to Azure Active Directory.
  • Administrators can provide conditional access based on application resource, device and user identity, network location and multifactor authentication.
  • Users can leverage their common identity through accounts in Azure AD to Office 365, Intune, SaaS apps and third-party applications.
  • Developers can build applications that leverage the common identity model, integrating applications into Active Directory on-premises or Azure for cloud-based applications

Azure AD Connect makes this integration easy and simplifies the management of your on-premises and cloud identity infrastructure.

Download "Microsoft Azure Active Directory Connect"

Azure AD Connect: Version Release History

1.1.180.0

Released: 2016 May

New features:

Fixed issues and improvements:

  • Added filtering to the Sync Rule Editor to make it easy to find sync rules.  (see figure 1 below)
  • Improved performance when deleting a connector space.
  • Fixed an issues when the same object was both deleted and added in the same run (called delete/add).
  • A disabled Sync Rule will no longer re-enable included objects and attributes on upgrade or directory schema refresh.

image

Figure 1: Sync Rule Editor With New Filtering Options To Find Sync Rules More Easy

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Azure AD Connect, Windows Azure Active Directory | Leave a Comment »

(2016-05-12) Upgrading The MFA Server Components From v6.3.0 To v7.0.0

Posted by Jorge on 2016-05-12


Microsoft released a newer version (v7.0.0.0) of the on-premises Azure AD MFA server a while back. If you are currently using v6.3.0, then this blog post will help you upgrade from v6.3.0 to v7.0.0.

First download the latest version of the Azure MFA Server installation program “MultiFactorAuthenticationServerSetup.exe”

  1. Navigate to https://manage.windows.azure.com/ and login with your administrator credentials
  2. On the navigation bar on left click on “All Items”
  3. On the main screen click on the directory item
  4. At the top of the screen click on the item called “Configure”
  5. Scroll to the section called “Multi-Factor Authentication”
  6. Within that section click on “Manage Service Settings” (a new tab opens)
  7. At the bottom of the screen click on “Go To The Portal” (a new tab opens)
  8. Somewhere in the middle of the screen click on “Downloads”
  9. Somewhere in the middle of the screen click on “Download” to download the latest version of the Azure MFA Server installation program

REMARK: If this would be a first time installation, you would also need the activation credentials by clicking on “Generate Activation Credentials”. During the upgrade the Activation Credentials are not needed.

Move the downloaded file to the servers already running the Azure AD MFA Server bits.

Execute the following actions on every Azure AD MFA server you have. Start with the primary/master server, and when fully finished, move to the next secondary Azure AD MFA server. By the way, this does assume all Azure AD MFA server components (MFA server, Web Service SDK, User Portal and Mobile App Service) are installed on the same server, whether you have just one server or multiple servers.

Start the MFA admin console. Then click on the Status icon on the left at the top to see which servers exist. Write down the list of MFA servers

image

Figure 1: The MFA Admin Console Showing The Status Of Every MFA Server

Execute the following actions on every MFA server, but do it one at a time in the order explained below!

Hotfix Pre-Requisite

Make sure the hotfix MS-KBQ2919355 has been installed already. If not installed it first!

To check if the hotfix is already installed or not, execute:

Get-HotFix -Id KB2919355

image

Figure 2: Checking If The Hotfix MSKB-Q2919355 Is Installed

Installing The MFA Server Bits

Double-click on “MultiFactorAuthenticationServerSetup.exe”

You will see the warning regarding the hotfix MS-KBQ2919355.

Click [OK] to continue

image

Figure 3: Warning Regarding The Hotfix MSKB-Q2919355 Requirement

If “Visual C++ Redistributable for Visual Studio 2015 Update 1” is not installed you will see the following screen.

If your MFA servers are only allowed to connect to Azure AD for the Azure AD MFA service and you cannot connect to other URLs, download the “Visual C++ Redistributable for Visual Studio 2015 Update 1” (both x86 and x64!) yourself from https://www.microsoft.com/en-us/download/details.aspx?id=49984.

Click [Install].

image
Figure 4: Warning About The Pre-Requisite Installation Of “Visual C++ Redistributable For Visual Studio 2015 Update 1”

Check “I Agree To The License Terms And Conditions”

Click [Install].

image

Figure 5: Warning About The Pre-Requisite Installation Of “Visual C++ Redistributable For Visual Studio 2015 Update 1” (x64)

Click [Close].

image

Figure 6: Finishing The Installation Of “Visual C++ Redistributable For Visual Studio 2015 Update 1” (x64)

Check “I Agree To The License Terms And Conditions”

Click [Install].

image

Figure 7: Warning About The Pre-Requisite Installation Of “Visual C++ Redistributable For Visual Studio 2015 Update 1” (x86)

Click [Close].

image

Figure 8: Finishing The Installation Of “Visual C++ Redistributable For Visual Studio 2015 Update 1” (x86)

Click [Next >].

image

Figure 9: Starting The Install Of The Azure AD MFA Server Bits

Click [Finish].

image

Figure 10: Finishing The Install Of The Azure AD MFA Server Bits

The MFA Admin Console will start and show the following message If the user portal is installed

Click [No].

image

Figure 11: Notification About A Newer Version Of The User Portal

…and show the following message If the web service SDK is installed

Click [No].

image

Figure 12: Notification About A Newer Version Of The Web Service SDK

Close the MFA admin console

Updating The Web Service SDK

Navigate to the folder “C:\Program Files\Multi-Factor Authentication Server” (=default location) and double-click on “MultiFactorAuthenticationWebServiceSdkSetup64.msi”

Make sure to select the correct web site, the correct virtual directory and the correct application pool. If you are uncertain, Open up IIS Manager, select the correct virtual directory/application and through the advanced settings check the correct settings FIRST!!!

Click [Next>]

image

Figure 13: Configuring The Web Service SDK Installation

Click [Close]

image

Figure 14: Finishing The Installation Of The Web Service SDK

Update the User Portal

Navigate to the folder “C:\Program Files\Multi-Factor Authentication Server” (=default location) and double-click on “MultiFactorAuthenticationUserPortalSetup64.msi”

Make sure to select the correct web site, the correct virtual directory and the correct application pool. If you are uncertain, Open up IIS Manager, select the correct virtual directory/application and through the advanced settings check the correct settings FIRST!!!

Click [Next>]

image

Figure 15: Configuring The User Portal Installation

Click [Close]

image

Figure 16: Finishing The Installation Of The User Portal

Update the Mobile App Web Service

Navigate to the folder “C:\Program Files\Multi-Factor Authentication Server” (=default location) and double-click on “Double-click on MultiFactorAuthenticationMobileAppWebServiceSetup64.msi”

Make sure to select the correct web site, the correct virtual directory and the correct application pool. If you are uncertain, Open up IIS Manager, select the correct virtual directory/application and through the advanced settings check the correct settings FIRST!!!

Click [Next>]

image

Figure 17: Configuring The Mobile Web App Web Service

Click [Close]

image

Figure 18: Finishing The Installation Of The Mobile Web App Web Service

Configuring Applications Pools

After the installation when you open IIS manager and check the application pools, you will see the following new application pools (or similar) with the same account as the previous application pools:

  • ASP.NET v4.0 MultiFactorAuthWebServiceSdk
  • ASP.NET v4.0 MultiFactorAuthUserPortal
  • ASP.NET v4.0 MultiFactorAuthPhoneAppWebService

Select the virtual directory/application “MultiFactorAuthWebServiceSdk”, click Advanced Settings on the right. Click on “Application Pool” at the top and select the application pool “ASP.NET v4.0 MultiFactorAuthWebServiceSdk”

Select the virtual directory/application “MultiFactorAuthUserPortal”, click Advanced Settings on the right. Click on “Application Pool” at the top and select the application pool “ASP.NET v4.0 MultiFactorAuthUserPortal”

Select the virtual directory/application “MultiFactorAuthPhoneAppWebService”, click Advanced Settings on the right. Click on “Application Pool” at the top and select the application pool “ASP.NET v4.0 MultiFactorAuthPhoneAppWebService”

Restart the “Default Web Site”.

Now update all other MFA servers, one at a time and in the same order as explained above!

Updating The ADFS Adapter

On one of the Azure AD MFA servers, navigate to the folder “C:\Program Files\Multi-Factor Authentication Server” (=default location) and copy the files “MultiFactorAuthenticationAdfsAdapterSetup64.msi” and“MultiFactorAuthenticationAdfsAdapter.config” to all ADFS servers!

The following actions will have a negative impact on the Azure AD MFA Provider within ADFS. It will not impact ADFS itself, but any application configured to enforce MFA whereas the users must use Azure AD MFA is not possible until everything has finished!!! THEREFORE PLAN THIS ACCORDINGLY!!!

REMARK: you may have seen the suggestion on other blog posts to remove the ADFS server from the farm to lower the impact. Do not do that! That will impact ADFS itself, and it you only have one ADFS server your farm will die!

First you need to understand if you are using WID or SQL. And if you are using WID, you need to find the primary ADFS server.

To understand if you are using WID or SQL see: (2014-03-17) Gathering Architectural Details From Your ADFS Infrastructure – ADFS Config DB On WID Or SQL

When using WID, to find your primary server see: (2014-03-19) Gathering Architectural Details From Your ADFS Infrastructure – WID Primary Computer Or Not

We need to unselect and unregister the previous ADFS adapter

Using WID?: Execute on primary ADFS server and wait at least 5 minutes to allow WID replication to take place and finish

Using SQL?: Execute on any ADFS server

# Unselecting The Use Of Azure AD MFA Adapter To Be Listed
$listOfCurrentMFAProviders = (Get-AdfsGlobalAuthenticationPolicy).AdditionalAuthenticationProvider
$listOfNewMFAProviders = $listOfCurrentMFAProviders
$listOfNewMFAProviders.Remove("WindowsAzureMultiFactorAuthentication")
Set-AdfsGlobalAuthenticationPolicy -AdditionalAuthenticationProvider $listOfNewMFAProviders

# Unregistering The Azure AD MFA Adapter Within ADFS
Unregister-AdfsAuthenticationProvider -Name WindowsAzureMultiFactorAuthentication

DO NOT restart the ADFS service as requested!

Again, execute the following on every ADFS server, starting with the primary if you are using WID, and if you are using SQL just pick a first ADFS server. After that, repeat on every other ADFS server.

If “Visual C++ Redistributable for Visual Studio 2015 Update 1” is not installed you will need to install it first before continuing. Unlike the Azure AD MFA server bits, the ADFS adapter v7.0.0 does not perform a pre-requisite check and does not offer to install the pre-requisites. Without it, the complete installation will succeed. However, as soon as you restart the ADFS service, the ADFS Admin logs will have errors while loading the new MFA provider due to files not found.

Navigate to https://www.microsoft.com/en-us/download/details.aspx?id=49984 and download the “Visual C++ Redistributable for Visual Studio 2015 Update 1” (both x86 and x64!). Move both files to every ADFS server you have.

Navigate to the folder you moved the “Visual C++ Redistributable for Visual Studio 2015 Update 1” (both x86 and x64!) to, and double-click on “VC_redist.x64.exe”. You will see the same screens as figure 5 and 6.

Navigate to the folder you moved the “Visual C++ Redistributable for Visual Studio 2015 Update 1” (both x86 and x64!) to, and double-click on “VC_redist.x86.exe”. You will see the same screens as figure 7 and 8.

Navigate to the folder you moved the “MultiFactorAuthenticationAdfsAdapterSetup64.msi” to, and double-click on “MultiFactorAuthenticationAdfsAdapterSetup64.msi”. This will replace the pervious adapter files and that’s OK. If you receive a notification the files are in use by the “Active Directory Federation Service”, then click continue.

Click [Next >]

image

Figure 19: Starting The Installation Of The ADFS Adapter For Azure AD MFA

Click [Close]

image

Figure 20: Finishing The Installation Of The ADFS Adapter For Azure AD MFA

Now update all other ADFS servers, one at a time and in the same order as explained above!

Using WID?: EDIT The file “MultiFactorAuthenticationAdfsAdapter.config” on the primary ADFS server

Using SQL?: EDIT The file “MultiFactorAuthenticationAdfsAdapter.config” on any ADFS server

On the first yellow marked line, configure as true

On the second yellow marked line, configure the URL to the Azure AD MFA Web Service SDK

On the third yellow marked line, configure the account (DOMAIN\SAMACCOUNTNAME) the user portal is also using in its web.config

On the fourth yellow marked line, configure the password of the account above the user portal is also using in its web.config

Save the file

image

Figure 21: Editing The file “MultiFactorAuthenticationAdfsAdapter.config”

Now we need to register the new ADFS adapter

# Registering The Azure AD MFA Adapter Within ADFS
$typeName = "pfadfs.AuthenticationAdapter, MultiFactorAuthAdfsAdapter, Version=7.0.0.9, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
Register-AdfsAuthenticationProvider -TypeName $typeName -Name AzureMfaServerAuthentication  -ConfigurationFilePath "MultiFactorAuthenticationAdfsAdapter.config"

DO NOT restart the ADFS service as requested!

If you previously configured a custom display name and a description for the Azure AD MFA adapter then do that also now. Replace the yellow marked text below with what you need/want

Set-AdfsAuthenticationProviderWebContent -Name "AzureMfaServerAuthentication" -DisplayName "Azure AD MFA AuthN" -Description "Phone Call, SMS, Software Based OTP Or Push Message Verified Authentication By Azure AD"

Now we need to select the new ADFS adapter

# Selecting The Use Of Azure AD MFA Adapter To Be Listed
$listOfCurrentMFAProviders = (Get-AdfsGlobalAuthenticationPolicy).AdditionalAuthenticationProvider
$listOfNewMFAProviders = $listOfCurrentMFAProviders + "AzureMfaServerAuthentication"
Set-AdfsGlobalAuthenticationPolicy -AdditionalAuthenticationProvider $listOfNewMFAProviders

If you are using WID, wait first at least 5 minutes and then execute the command below (this allows WID replication to have taken place)

If you are using SQL, execute the following on any ADFS server (no need to wait as with WID)

Restart The ADFS Service

Restart-Service ADFSSRV -Force

Check the ADFS Admin Event for any error (when using WID you can expect to see an error regarding the unavailability of Artifact Resolution)

When everything is OK, you should be able to use the Azure AD MFA Adapter in ADFS

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Active Directory Federation Services (ADFS), Azure AD MFA Adapter, Multi-Factor AuthN, Windows Azure Active Directory | Leave a Comment »

(2016-05-10) Azure AD Identity Protection At A Glance

Posted by Jorge on 2016-05-10


Read how Azure AD Identity Protection protects your MSA and AAD accounts here.

And this is the why Azure AD Identity Protection is so important!

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Azure AD Identity Protection, Windows Azure Active Directory | Leave a Comment »

(2016-05-09) Notifying Users By E-mail Their Password Is Going To Expire (Update 5)

Posted by Jorge on 2016-05-09


Around 2009 I wrote a blog post about and also wrote a tool to notify users through e-mail when their password was going to expire. You can read all the details about the idea here. Now that tool was very inflexible and because of that I received numerous requests to make it more flexible such as the ability to customize the e-mail message. With this blog post I’m sharing a brand new tool, based upon PowerShell, that will notify users through e-mail when their password is going to expire. So let’s get started in explaining on this works! I did not test all combinations! However, I do expect it to run on any Windows version as long as PowerShell is available. It should also work against any AD version and there is NO dependency on using the AD PowerShell CMDlets!. Everything is done through ADSI to be independent of Windows versions! It will also support PSOs if the DFL is high enough and PSOs are configured!

SYNTAX:

  • <PoSH Script File> –> Runs The Script In Test Mode While NOT Sending Any E-Mails
  • <PoSH Script File> -force:$true –> Runs The Script In DEV (One Mail To Configured Admin) Or TEST (All Mails To Configured Admin) Or PROD (Mails To Users) Mode While Sending Any E-Mails

Please provide feedback through the comments section OR you the contact page

DISCLAIMER (READ THIS!):

  • I wrote this script, therefore I own it. Anyone asking money for it, should NOT be doing that and is basically ripping you off!
  • The script is freeware, you are free to use it and distribute it, but always refer to this website (https://jorgequestforknowledge.wordpress.com/) as the location where you got it.
  • This script is furnished "AS IS". No warranty is expressed or implied!
  • I have NOT tested it in every scenario nor have I tested it against every Windows and/or AD version
  • Always test first in lab environment to see if it meets your needs!
  • Use this script at your own risk!
  • I do not warrant this script to be fit for any purpose, use or environment!
  • I have tried to check everything that needed to be checked, but I do not guarantee the script does not have bugs!
  • I do not guarantee the script will not damage or destroy your system(s), environment or whatever!
  • I do not accept liability in any way if you screw up, use the script wrong or in any other way where damage is caused to your environment/systems!
  • If you do not accept these terms do not use the script in any way and delete it immediately!

REMARKS (READ THIS!):

  • The script requires PowerShell v2.0 at a minimum
  • This script must be able to read the contents of the PSO container in every AD domain the script will target!. By default only Domain Admins can read this.
  • It is therefore needed to delegate those permissions to the account executing this PoSH script.
  • For more information about this see the blog post: https://jorgequestforknowledge.wordpress.com/2007/08/09/windows-server-2008-fine-grained-password-policies/
  • DSACLS "\\<Some RWDC>\CN=Password Settings Container,CN=System,<Your AD domain DN>" /G "<Some Security Principal>:GR" /I:T
    • This assign <Some Security Principal> with Allow:Read on the Password Settings Container including its descendant objects

The tool uses an XML called "AD-Pwd-Exp-Notify.xml". It is pre-filled with examples from my test/demo environment. Make sure to change as needed to accommodate your own environment and requirements!

The script has four execution modes. When NOT running the PowerShell script with the ‘-force’ parameter, it will by default run in TEST mode without sending any e-mail to users regarding password expiry ("TEST (NO MAILINGS)"), no matter what the configuration in the XML files specifies. When running the PowerShell script with the ‘-force’ parameter, it will look in the XML file to see which execution mode to run in. When "DEV" is specified it will only send 1 mail to the SMTP address of the admin user specified in the "toSMTPAddressInTestMode" configuration field. This mode allows you to develop the solution being swamped in e-mails or impacting your users. When "TEST" is specified it will only send all mails to the SMTP address of the admin user specified in the "toSMTPAddressInTestMode" configuration field. This mode allows you to see/experience what your scoped/targeted users would see/experience without actually impacting them. When "PROD" is specified it will only send all mails to the SMTP address of the individual users. This really sends the e-mails to all the scoped/targeted individual users.

<!– Execution Mode: DEV (1 Mail To Admin User) or TEST (All Mails To Admin User) or PROD (All Mails To Individual Users) –>

<executionMode>DEV</executionMode>

The PowerShell script sends e-mail, therefore it requires a FROM e-mail address

<!– The SMTP Address Used In The FROM Field –>

<mailFromSender>general.DO-NOT-REPLY@iamtec.nl</mailFromSender>

To develop the solution and test it you can specify an SMTP address that will be used to send e-mails to, without impacting the real user community. That SMTP address will also be used for notifications is the SMTP server or DC is unavailable.

<!– The SMTP Address Used When Running In DEV/TEST Mode And Also Used For Notifications –>

<toSMTPAddressInTestMode>adm.root@iamtec.nl</toSMTPAddressInTestMode>

The PowerShell script sends e-mail, therefore it requires an SMTP server. A test connection to the SMTP server is made. If it fails the script aborts!

<!– FQDN Of The Mail Server Or Mail Relay –>

<smtpServer>MAIL.IAMTEC.NET</smtpServer>

The priority of the mail send can be configured as Low, Normal or High

<!– The Priority Of The Message: Low, Normal, High –>

<mailPriority>High</mailPriority>

The script supports multi-lingual messages. You must always specify a default language and for each language you must also specify a mail subject and the full path to HTML body file that contains the text in a specific language. Both the subject and the body support variables that can be replaced by the actual values. The script contains an example for US (English) and the same example for NL (Dutch).

<!– The File With The HTML Body Text For A Specific Language And The Subject. Supported Variables: FIRST_NAME, LAST_NAME, DISPLAY_NAME, FQDN_DOMAIN, PWD_EXPIRE_IN_NUM_DAYS, PWD_EXPIRY_DATE, PWD_MIN_LENGTH, PWD_MIN_AGE, PWD_MAX_AGE, PWD_HISTORY, PWD_COMPLEX, PWD_CHANGE_RESET_URL  –>
<htmlBodyFiles>
    <htmlBodyFile language="default" mailSubject="Expiring Password In Approx. PWD_EXPIRE_IN_NUM_DAYS Days – Change Your Password As Soon As Possible!" fullPath="D:\TEMP\ADPwdExpNotifyMessageBody_US.html" />
    <htmlBodyFile language="US" mailSubject="Expiring Password In Approx. PWD_EXPIRE_IN_NUM_DAYS Days – Change Your Password As Soon As Possible!" fullPath="D:\TEMP\ADPwdExpNotifyMessageBody_US.html" />
    <htmlBodyFile language="NL" mailSubject="Verlopen Wachtwoord In Ongeveer PWD_EXPIRE_IN_NUM_DAYS Dagen – Wijzig Uw Wachtwoord Zo Snel Als Mogelijk!" fullPath="D:\TEMP\ADPwdExpNotifyMessageBody_NL.html" />
</htmlBodyFiles>

If you have a web portal (e.g. FIM SSPR or Exchange Change Password) to change and/or reset the password, then you can specify it here

<!– The URL Where The Users Can Change Or Reset Their Password –>

<pwdChangeOrResetURL>https://ssprportal.iamtec.net:447/</pwdChangeOrResetURL>

Logging to screen can be enabled (ON) or disabled (OFF).

<!– Enable/Disable Logging To Screen: ON or OFF –>

<logToScreen>ON</logToScreen>

Logging tofile can be enabled (ON) or disabled (OFF).

<!– Enable/Disable Logging To A Log File: ON or OFF –>

<logToFile>ON</logToFile>

If logging is enabled, then you must specify the full path to the log file that will be used. The script itself will take date and time into account

<!– Full Path Of The Log File (.LOG Extension!) –>

<fullPathToLogFile>D:\TEMP\ADPwdExpNotify.log</fullPathToLogFile>

To make sure the disk is not swamped with a huge number of log files, you can specify for how many days the script will keep log files. Every log file older than the specified number will be deleted

<!– Number Of Days To Keep LOG Files –>

<numDaysLOGToKeep>30</numDaysLOGToKeep>

When enabled (ON), the script will export the information of users to a CSV file for troubleshooting and analyses. When disabled (OFF) nothing is exported.

<!– Enable/Disable Export Of Notified Accounts To A CSV File: ON or OFF –>

<exportToCSV>ON</exportToCSV>

If exporting is enabled, then you must specify the full path to the CSV file that will be used. The script itself will take date and time into account

<!– Full Path Of The CSV File (.CSV Extension!) –>

<fullPathToCSVFile>D:\TEMP\ADPwdExpNotify.csv</fullPathToCSVFile>

To make sure the disk is not swamped with a huge number of CSV files, you can specify for how many days the script will keep CSV files. Every CSV file older than the specified number will be deleted

<!– Number Of Days To Keep CSV Files –>

<numDaysCSVToKeep>30</numDaysCSVToKeep>

In the XML config file you can specify the date/time format to be used on screen, in the log, in the CSV and in the E-mail message

<!– Date And Time Format To Use On Screen, In Logs And In E-mail Message –>
<formatDateTime>yyyy-MM-dd HH:mm:ss</formatDateTime>

In this section you specify every AD domain in the AD forest, including trusting AD domains, for which its scoped/targeted users must be notified. For every AD domain, you then need to specify if an RWDC needs to be discovered (specifiy: DISCOVER) or you can specifically mention an RWDC that must be targeted. A test connection to the RWDC is made. If it fails the AD domain for that RWDC is fully skipped! Then for every AD domain, specify one or more search bases to scope/target users in the LDAP query. For every search base configure the language for the scoped/targeted users. Make sure that if you specify specific languages, that you also have configured the HTML Body File for that language

<!– Targeted Domains, Specify DISCOVER To Discover A DC Or Use Specific DC And Search Bases Per Domain –>
<!– WARNING: Make Sure The Search Bases DO NOT Overlap Each Other!!! –>
<domains>
    <domain FQDN="IAMTEC.NET" DC="DISCOVER">
        <searchBase nr="1" language="default">OU=Users,OU=EMPLOYEES,OU=Org-Users,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="2" language="US">OU=Users,OU=CONTRACTORS,OU=Org-Users,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="3" language="US">OU=Users,OU=CONTRACTORZZZ,OU=Org-Users,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="4" language="NL">OU=Users,OU=HISTORY1,OU=Org-Users,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="5" language="NL">OU=Users,OU=HISTORY2,OU=Org-Users,DC=IAMTEC,DC=NET</searchBase>
    </domain>
    <domain FQDN="CHILD.IAMTEC.NET" DC="C1FSRWDC1.CHILD.IAMTEC.NET">
        <searchBase nr="1" language="default">OU=Users,OU=EMPLOYEES,OU=Org-Users,DC=CHILD,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="2" language="US">OU=Users,OU=CONTRACTORS,OU=Org-Users,DC=CHILD,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="3" language="US">OU=Users,OU=CONTRACTORZZZ,OU=Org-Users,DC=CHILD,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="4" language="NL">OU=Users,OU=HISTORY1,OU=Org-Users,DC=CHILD,DC=IAMTEC,DC=NET</searchBase>
        <searchBase nr="5" language="NL">OU=Users,OU=HISTORY2,OU=Org-Users,DC=CHILD,DC=IAMTEC,DC=NET</searchBase>
    </domain>
</domains>

In this section you can specify one or multiple periods of notifications. Make sure that none of the periods overlaps any other period!. In the example shown, the scoped/targeted user will receive 4 notifications assuming the script executes once every day. Also make sure the periods configured are in balance with the maximum password age!

<!– Number Of Days Before The Password Expires To Send Notifications –>
<!– WARNING: Make Sure The Periods DO NOT Overlap Each Other!!! –>
<daysBeforeWarn>
    <period nr="1" Max="10" Min="9" />
    <period nr="2" Max="5" Min="4" />
    <period nr="3" Max="2" Min="0" />
</daysBeforeWarn>

image_thumb26_thumb_thumb_thumb

Figure 1a: XML Configuration File

image_thumb29_thumb_thumb_thumb

Figure 1b: XML Configuration File

Executing The Script

Executing the script to run in "DEV", "TEST" or "PROD" mode (whatever is configured in the XML configuration file) while using the default location of the XML configuration file (same folder as script)

.\AD-Pwd-Exp-Notify_v018.ps1 -force

Executing the script to run in "TEST (NO MAILINGS)" mode while using the default location of the XML configuration file (same folder as script)

.\AD-Pwd-Exp-Notify_v018.ps1

Executing the script to run in "TEST (NO MAILINGS)" mode while using a custom location of the XML configuration file

.\AD-Pwd-Exp-Notify_v018.ps1 -xmlconfigfilepath D:\TEMP\AD-Pwd-Exp-Notify.xml

Executing the script to run in "DEV", "TEST" or "PROD" mode (whatever is configured in the XML configuration file) while using custom location of the XML configuration file

.\AD-Pwd-Exp-Notify_v018.ps1 -xmlconfigfilepath D:\TEMP\AD-Pwd-Exp-Notify.xml -force

Example Output Of The Script (On Screen)

image_thumb32_thumb_thumb_thumb

Figure 2a: Output To Screen

image_thumb35_thumb_thumb_thumb

Figure 2b: Output To Screen

image_thumb38_thumb_thumb_thumb

Figure 2c: Output To Screen

image_thumb41_thumb_thumb_thumb

Figure 2d: Output To Screen

image_thumb44_thumb_thumb_thumb

Figure 2e: Output To Screen

Example Output Of The Script (Log File)

See zip file

Example Output Of The Script (CSV file)

See zip file

E-mail Message For US English Language

image_thumb48_thumb_thumb_thumb

Figure 3a: E-Mail Notification In English

image_thumb51_thumb_thumb_thumb

Figure 3b: E-Mail Notification In English

E-mail Message For Dutch Language

image_thumb55_thumb_thumb_thumb

Figure 4a: E-Mail Notification In Dutch

image_thumb58_thumb_thumb_thumb

Figure 4b: E-Mail Notification In Dutch

And Finally….The PowerShell Script Itself

Version of version: v0.19

Date of script: 2016-05-09

### Abstract: This PoSH Script Notifies Mailbox Enabled Users For Which The Password Will Expires Within A Specific Number Of Days ### Written by: Jorge de Almeida Pinto [MVP-DS] ### BLOG: https://jorgequestforknowledge.wordpress.com/ ### ### 2015-03-21: Initial version of the script in PowerShell (v0.13) ### 2015-03-26: Bug fixes regarding some attributes not having values (v0.14) ### 2015-03-27: Supporting date/time format in XML and incorrect variable being used to get the correct password policy ### settings (v0.15) ### 2015-04-29: Bug fixes regarding the default domain GPO getting no name when no PSOs are used or inheriting the name ### of the last processed PSO, the displayName of the development user not being processed correctly, and more ### enhanced error detection when discovering a DC for non-existing AD domain, and better explanation and ### information about the parameters and the script itself (v0.16) ### 2015-09-22: Bug fixes regarding specifying the script version and date after the parameter section (v0.17) ### 2015-10-13: Resolved a bug with regards to paging when searching (v0.18) ### 2016-05-08: Resolved a bug with regards to array definition of the password policies in a domain ($pwdPolicyInDomain), ### and testing the existence of the language files (v0.19) ### <# .SYNOPSIS This PoSH Script Notifies Mailbox Enabled Users For Which The Password Will Expires Within A Specific Number Of Days .DESCRIPTION This PoSH script notifies mailbox enabled users for which the password will expires within a specific number of days. The configuration of the script is done through an XML file. The tool uses an XML called "AD-Pwd-Exp-Notify.xml". It is pre-filled with examples from my test/demo environment. Make sure to change as needed to accommodate your own environment and requirements! For detailed information about all configurable options see the sample XML file or browse to: https://jorgequestforknowledge.wordpress.com/2015/03/24/notifying-users-by-e-mail-their-password-is-going-to-expire-update-1/ .PARAMETER force Runs the script in whatever mode is configured in the XML file (e.g. "DEV", "TEST" or "PROD" mode) .PARAMETER xmlconfigfilepath Allows to use a custom location and custom XML file instead of the default XML file in the default location (same folder as the script) .EXAMPLE Executing the script to run in "TEST (NO MAILINGS)" mode while using the default location of the XML configuration file (same folder as script) AD-Pwd-Exp-Notify_vXXX.ps1 .EXAMPLE Executing the script to run in "DEV", "TEST" or "PROD" mode (whatever is configured in the XML configuration file) while using the default location of the XML configuration file (same folder as script) AD-Pwd-Exp-Notify_vXXX.ps1 -force .EXAMPLE Executing the script to run in "TEST (NO MAILINGS)" mode while using a custom location of the XML configuration file AD-Pwd-Exp-Notify_vXXX.ps1 -xmlconfigfilepath D:\TEMP\AD-Pwd-Exp-Notify.xml .EXAMPLE Executing the script to run in "DEV", "TEST" or "PROD" mode (whatever is configured in the XML configuration file) while using custom location of the XML configuration file AD-Pwd-Exp-Notify_vXXX.ps1 -xmlconfigfilepath D:\TEMP\AD-Pwd-Exp-Notify.xml -force .NOTES -->> DISCLAIMER <<-- * I wrote this script, therefore I own it. Anyone asking money for it, should NOT be doing that and is basically ripping you off! * The script is freeware, you are free to use it and distribute it, but always refer to this website (https://jorgequestforknowledge.wordpress.com/) as the location where you got it. * This script is furnished "AS IS". No warranty is expressed or implied! * I have NOT tested it in every scenario nor have I tested it against every Windows and/or AD version * Always test first in lab environment to see if it meets your needs! * Use this script at your own risk! * I do not warrant this script to be fit for any purpose, use or environment! * I have tried to check everything that needed to be checked, but I do not guarantee the script does not have bugs! * I do not guarantee the script will not damage or destroy your system(s), environment or whatever! * I do not accept liability in any way if you screw up, use the script wrong or in any other way where damage is caused to your environment/systems! * If you do not accept these terms do not use the script in any way and delete it immediately! -->> REMARKS <<-- * The script requires PowerShell v2.0 at a minimum * This script must be able to read the contents of the PSO container in every AD domain the script will target!. By default only Domain Admins can read this. * It is therefore needed to delegate those permissions to the account executing this PoSH script. * For more information about this see the blog post: https://jorgequestforknowledge.wordpress.com/2007/08/09/windows-server-2008-fine-grained-password-policies/ * DSACLS "\\<Some RWDC>\CN=Password Settings Container,CN=System,<Your AD domain DN>" /G "<Some Security Principal>:GR" /I:T >> This assigns <Some Security Principal> with Allow:Read on the Password Settings Container including its descendant objects #> Param( [Parameter(Mandatory=$false)] [string]$xmlconfigfilepath, [Parameter(Mandatory=$false)] [switch]$force ) $scriptVersion = "v0.19" $scriptDate = "2016-05-08" ################################################################################################## #################################### SCRIPT FUNCTIONS START ###################################### ################################################################################################## ################################################################################################## ### FUNCTION: Logging Data To The Log File Function Logging($dataToLog) { $datetimeLogLine = "[" + $(Get-Date -format $formatDateTime) + "] : " If ($logToFile.ToUpper() -eq "ON") { Out-File -filepath "$fullPathToLogFile" -append -inputObject "$datetimeLogLine$dataToLog" } If ($logToScreen.ToUpper() -eq "ON") { Write-Output($datetimeLogLine + $dataToLog) } } ################################################################################################## ### FUNCTION: Cleaning Up Old Log Files Function CleanUpLOGFiles($numDaysLOGToKeep) { $regExPatternLogFile = '^.*_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}.log$' $oldLogFiles = Get-ChildItem -Path $folderLogFile\*.log | ?{$_.Name -match $regExPatternLogFile} $oldLogFilesToDelete = $oldLogFiles | ?{$_.lastwritetime -lt (Get-Date $execStartDateTime).addDays(-$numDaysLOGToKeep) -and -not $_.psiscontainer} $oldLogFilesToDelete | %{Remove-Item $_.FullName -force} Return ($oldLogFilesToDelete | Measure-Object).Count } ################################################################################################## ### FUNCTION: Cleaning Up Old Csv Files Function CleanUpCSVFiles($numDaysCSVToKeep) { $regExPatternCsvFile = '^.*_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}.csv$' $oldCsvFiles = Get-ChildItem -Path $folderCsvFile\*.csv | ?{$_.Name -match $regExPatternCsvFile} $oldCsvFilesToDelete = $oldCsvFiles | ?{$_.lastwritetime -lt (Get-Date $execStartDateTime).addDays(-$numDaysCSVToKeep) -and -not $_.psiscontainer} $oldCsvFilesToDelete | %{Remove-Item $_.FullName -force} Return ($oldCsvFilesToDelete | Measure-Object).Count } ################################################################################################## ### FUNCTION: Discover An RWDC From An AD Domain Function DiscoverRWDC($fqdnADdomain) { $contextADDomain = $NULL $contextADDomain = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("Domain",$fqdnADdomain) $dnsHostNameRWDC = $NULL $ErrorActionPreference = "SilentlyContinue" $dnsHostNameRWDC = ([System.DirectoryServices.ActiveDirectory.DomainController]::findone($contextADDomain)).Name $ErrorActionPreference = "Continue" If ($dnsHostNameRWDC -eq $null) { Return "DOMAIN_DOES_NOT_EXIST_OR_CANNOT_FIND_DC" } Else { Return $dnsHostNameRWDC } } ################################################################################################## ### FUNCTION: Check If An OU/Container Exists Function CheckDNExistence($dnsHostNameRWDC,$DN) { Try { If([ADSI]::Exists("LDAP://$dnsHostNameRWDC/$DN")) { Return "SUCCESS" } Else { Return "ERROR" } ` } Catch { Return "ERROR" } } ################################################################################################## ### FUNCTION: Test Connection To A Server Function TestConnectionToServer($dnsHostName,$port) { $tcpPortSocket = New-Object System.Net.Sockets.TcpClient $timeOut = "500" $portConnect = $tcpPortSocket.BeginConnect($dnsHostName,$port,$null,$null) $tcpPortWait = $portConnect.AsyncWaitHandle.WaitOne($timeOut,$false) If(!$tcpPortWait) { $tcpPortSocket.Close() Return "ERROR" } Else { $ErrorActionPreference = "SilentlyContinue" $tcpPortSocket.EndConnect($portConnect) | Out-Null If (!$?) { Return "ERROR" } Else { Return "SUCCESS" } $tcpPortSocket.Close() $ErrorActionPreference = "Continue" } } ################################################################################################## ### FUNCTION: Decode Functional Level Function DecodeFunctionalLevel($dfl) { Switch ($dfl) { 0 {"Windows 2000"} 1 {"Windows 2003 Interim"} 2 {"Windows 2003"} 3 {"Windows 2008"} 4 {"Windows 2008 R2"} 5 {"Windows 2012"} 6 {"Windows 2012 R2"} #7 {"TBD"} #8 {"TBD"} #9 {"TBD"} #10 {"TBD"} Default {"If You See This, Something Is Wrong!"} } } ################################################################################################## ##################################### SCRIPT FUNCTIONS END ####################################### ################################################################################################## ### Clear The Screen Clear-Host ### Configure The Appropriate Screen And Buffer Size To Make Sure Everything Fits Nicely $uiConfig = (Get-Host).UI.RawUI $uiConfig.WindowTitle = "+++ AD PASSWORD EXPIRY NOTIFICATION +++" $uiConfig.ForegroundColor = "Yellow" $uiConfigBufferSize = $uiConfig.BufferSize $uiConfigBufferSize.Width = 500 $uiConfigBufferSize.Height = 9999 $uiConfigScreenSizeMax = $uiConfig.MaxPhysicalWindowSize $uiConfigScreenSizeMaxWidth = $uiConfigScreenSizeMax.Width $uiConfigScreenSizeMaxHeight = $uiConfigScreenSizeMax.Height $uiConfigScreenSize = $uiConfig.WindowSize If ($uiConfigScreenSizeMaxWidth -lt 160) { $uiConfigScreenSize.Width = $uiConfigScreenSizeMaxWidth } Else { $uiConfigScreenSize.Width = 160 } If ($uiConfigScreenSizeMaxHeight -lt 75) { $uiConfigScreenSize.Height = $uiConfigScreenSizeMaxHeight - 5 } Else { $uiConfigScreenSize.Height = 75 } $uiConfig.BufferSize = $uiConfigBufferSize $uiConfig.WindowSize = $uiConfigScreenSize ### Script Configuration File If ($xmlconfigfilepath -eq $null -or $xmlconfigfilepath -eq "") { $currentScriptFolderPath = Split-Path $MyInvocation.MyCommand.Definition [string]$scriptXMLConfigFilePath = Join-Path $currentScriptFolderPath "AD-Pwd-Exp-Notify.xml" } Else { [string]$scriptXMLConfigFilePath = $xmlconfigfilepath } ### Start Time Of Script In UTC $execStartDateTime = (Get-Date -format $formatDateTime) $execStartDateTimeForFileSystem = (Get-Date $execStartDateTime -format "yyyy-MM-dd_HH-mm-ss") ### Read The Config File If (!(Test-Path $scriptXMLConfigFilePath)) { Write-Host "The XML Config File '$scriptXMLConfigFilePath' CANNOT Be Found!..." -ForeGroundColor Red Write-Host "Aborting Script..." -ForeGroundColor Red EXIT } Else { [XML]$global:configADPwdExpNotify = Get-Content $scriptXMLConfigFilePath #Write-Host "The XML Config File '$scriptXMLConfigFilePath' Has Been Found!..." -ForeGroundColor Green #Write-Host "Continuing Script..." -ForeGroundColor Green #Write-Host "" } ### Read The Properties From The XML Config File $executionMode = $configADPwdExpNotify.ADPwdExpNotifyConfig.executionMode $mailFromSender = $configADPwdExpNotify.ADPwdExpNotifyConfig.mailFromSender $toSMTPAddressInTestMode = $configADPwdExpNotify.ADPwdExpNotifyConfig.toSMTPAddressInTestMode $smtpServer = $configADPwdExpNotify.ADPwdExpNotifyConfig.smtpServer $mailPriority = $configADPwdExpNotify.ADPwdExpNotifyConfig.mailPriority $mailSubject = $configADPwdExpNotify.ADPwdExpNotifyConfig.mailSubject $htmlBodyFiles = $configADPwdExpNotify.ADPwdExpNotifyConfig.htmlBodyFiles.htmlBodyFile $pwdChangeOrResetURL = $configADPwdExpNotify.ADPwdExpNotifyConfig.pwdChangeOrResetURL $logToScreen = $configADPwdExpNotify.ADPwdExpNotifyConfig.logToScreen $logToFile = $configADPwdExpNotify.ADPwdExpNotifyConfig.logToFile $fullPathToLogFile = $configADPwdExpNotify.ADPwdExpNotifyConfig.fullPathToLogFile -replace ".log","_$execStartDateTimeForFileSystem.log" $folderLogFile = Split-Path $fullPathToLogFile $numDaysLOGToKeep = $configADPwdExpNotify.ADPwdExpNotifyConfig.numDaysLOGToKeep $exportToCSV = $configADPwdExpNotify.ADPwdExpNotifyConfig.exportToCSV $fullPathToCSVFile = $configADPwdExpNotify.ADPwdExpNotifyConfig.fullPathToCSVFile -replace ".csv","_$execStartDateTimeForFileSystem.csv" $folderCsvFile = Split-Path $fullPathToCSVFile $numDaysCSVToKeep = $configADPwdExpNotify.ADPwdExpNotifyConfig.numDaysCSVToKeep $formatDateTime = $configADPwdExpNotify.ADPwdExpNotifyConfig.formatDateTime $domains = $configADPwdExpNotify.ADPwdExpNotifyConfig.domains.domain $daysBeforeWarn = $configADPwdExpNotify.ADPwdExpNotifyConfig.daysBeforeWarn.Period Logging "#######################################################################################" Logging " *********************************************************" Logging " * *" Logging " * --> ACTIVE DIRECTORY PASSWORD EXPIRY NOTIFICATION <-- *" Logging " * *" Logging " * ($scriptVersion) ($scriptDate) *" Logging " * *" Logging " * Written By: Jorge de Almeida Pinto [MVP-DS] *" Logging " * *" Logging " * BLOG: 'Jorge's Quest For Knowledge' *" Logging " * (https://jorgequestforknowledge.wordpress.com/) *" Logging " * *" Logging " *********************************************************" Logging "" Logging "Starting Date And Time...........: $execStartDateTime" Logging "" $htmlBodyFiles | %{ $htmlBodyFileFullPath = $_.fullPath If (!(Test-Path $htmlBodyFileFullPath)) { Write-Host "" Write-Host "The Language File '$htmlBodyFileFullPath' Does Not Exist!..." -Foregroundcolor Red Write-Host "Aborting Script!..." -Foregroundcolor Red Write-Host "" EXIT } } Logging "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ### Logging All Configured Settings Logging "" Logging "XML Config File Path.............: $scriptXMLConfigFilePath" Logging "" If (!$force) { $executionMode = "TEST (NO MAILINGS)" } Logging "Execution Mode...................: $executionMode" Logging "" Logging "Log To Screen....................: $logToScreen" Logging "" Logging "Log To File......................: $logToFile" Logging "" Logging "Log File Full Path...............: $fullPathToLogFile" Logging "" Logging "Log Files Folder.................: $folderLogFile" Logging "" Logging "Number Of Days Of Logs To Keep...: $numDaysLOGToKeep" Logging "" Logging "Export List Of User To CSV.......: $exportToCSV" Logging "" Logging "CSV File Full Path...............: $fullPathToCSVFile" Logging "" Logging "CSV Files Folder.................: $folderCsvFile" Logging "" Logging "Number Of Days Of CSVs To Keep...: $numDaysCSVToKeep" Logging "" $smtpServerStatus = $NULL $smtpServerStatus = TestConnectionToServer $smtpServer "25" Logging "SMTP Server......................: $smtpServer (Status: $smtpServerStatus)" Logging "" If ($smtpServerStatus.ToUpper() -eq "ERROR") { EXIT } Logging "Sender Address...................: $mailFromSender" Logging "" If ($executionMode.ToUpper() -eq "TEST (NO MAILINGS)") { Logging "Recipient Address................: None" } If ($executionMode.ToUpper() -eq "DEV" -Or $executionMode.ToUpper() -eq "TEST") { Logging "Recipient Address................: $toSMTPAddressInTestMode" } If ($executionMode.ToUpper() -eq "PROD") { Logging "Recipient Address................: Individual Users" } Logging "" Logging "Message Priority.................: $mailPriority" $htmlBodyFiles | %{ $language = $_.language $mailSubject = $_.mailSubject $fullPath = $_.fullPath Logging "" Logging "Message Subject..................: ($language) $mailSubject" Logging "" Logging "HTML Body File...................: ($language) $fullPath" } Logging "" Logging "Change/Reset PWD URL.............: $pwdChangeOrResetURL" Logging "" Logging "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ### Cleaning Up Old Log and Csv Files Logging "" Logging "Cleaning Up Old Log Files. Keeping Log Files From Last $numDaysLOGToKeep Days..." $oldLogFilesToDeleteCount = CleanUpLOGFiles $numDaysLOGToKeep Logging " --> Number Of Old Log Files Deleted...: $oldLogFilesToDeleteCount" Logging "" Logging "Cleaning Up Old Csv Files. Keeping Csv Files From Last $numDaysCSVToKeep Days..." $oldCsvFilesToDeleteCount = CleanUpCSVFiles $numDaysCSVToKeep Logging " --> Number Of Csv Log Files Deleted...: $oldCsvFilesToDeleteCount" Logging "" Logging "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ### Creating An Empty Array For All Queried Users (Not Necessarily The Same List Of Users That Will Be Notified!) $listOfQueriedUsers = @() ### Processing Each Configured AD Domain In The XML Config File Logging "" Logging "Processing Configured AD Domains..." ### Go Through Every Configured AD Domain $domains | %{ ### The FQDN Of The AD Domain From The XML Config File $fqdnADdomain = $_.FQDN Logging "" Logging "** AD Domain: $fqdnADdomain **" ### The FQDN Of The DC From The XML Config File $fqdnDC = $_.DC ### If DISCOVER Was Specified Instead Of A Specific (Static) DC, Then Discover The Nearest RWDC And Use That One If ($fqdnDC.ToUpper() -eq "DISCOVER") { $fqdnDC = DiscoverRWDC $fqdnADdomain ### Check If The RWDC Is Available If ($fqdnDC -eq "DOMAIN_DOES_NOT_EXIST_OR_CANNOT_FIND_DC") { $dcStatus = "ERROR" Logging "" Logging " --> FQDN DC: $fqdnDC (Discovered) (Status: $dcStatus)" } Else { $dcStatus = $NULL $dcStatus = TestConnectionToServer $fqdnDC "389" Logging "" Logging " --> FQDN DC: $fqdnDC (Discovered) (Status: $dcStatus)" } } Else { ### Check If The RWDC Is Available $dcStatus = $NULL $dcStatus = TestConnectionToServer $fqdnDC "389" Logging "" Logging " --> FQDN DC: $fqdnDC (Static) (Status: $dcStatus)" } ### If There Is Something Wrong With The RWDC, Then Abort Processing For This AD Domain And Send Mail About It If ($dcStatus -eq "ERROR" -And $fqdnDC -ne "DOMAIN_DOES_NOT_EXIST_OR_CANNOT_FIND_DC") { Logging " --> SKIPPED DUE TO ERROR - UNABLE TO CONTACT DC!" Logging "" Send-MailMessage -SmtpServer $smtpServer -From $mailFromSender -To $toSMTPAddressInTestMode -Priority High -Subject "Error With DC From AD Domain!" -Body "There Are Connectivity Issues With The DC '$fqdnDC' From The AD Domain '$fqdnADdomain'!" -BodyAsHtml } If ($dcStatus -eq "ERROR" -And $fqdnDC -eq "DOMAIN_DOES_NOT_EXIST_OR_CANNOT_FIND_DC") { Logging " --> SKIPPED DUE TO ERROR - DOMAIN DOES NOT EXIST OR CANNOT FIND DC!" Logging "" Send-MailMessage -SmtpServer $smtpServer -From $mailFromSender -To $toSMTPAddressInTestMode -Priority High -Subject "Error With DC From AD Domain!" -Body "The AD Domain '$fqdnADdomain' Does Not Exist Or Unable To Discover A DC For The AD Domain '$fqdnADdomain'!" -BodyAsHtml } ### If The AD Domain Does Exist And The DC Can Be Discovered And It Can Be Contacted If ($dcStatus -eq "SUCCESS") { ### Array Definition Of All Password Policies In A Domain $pwdPolicyInDomain = @() ### Connect To The RootDSE Of The RWDC And Get Info From It $RootDSE = [ADSI]"LDAP://$fqdnDC/RootDSE" $dfl = $RootDSE.domainFunctionality $defaultNC = $RootDSE.defaultNamingContext Logging "" Logging " --> DFL: $dfl ($(DecodeFunctionalLevel $dfl))" Logging "" Logging " --> Default NC: $defaultNC" ### PWD Policies From The AD Domain ### If Domain Functional Level Is At Least 3 (Windows 2008) Or Higher Then Check For Any Configured Password Settings Object (PSO) And Get The Settings For Each PSO If ($dfl -ge 3) { Logging "" Logging " --> PSOs In AD Domain" ### PSO Container (REMEMBER: The Account Running This Script Must Have Allow:Read Permissions On The PSO Container Itself And Sub Objects $psoContainerDN = "CN=Password Settings Container,CN=System,$defaultNC" ### Setup The LDAP Query To Get All PSOs And Execute The Query $searchRoot = $NULL $searchRoot = [ADSI]"LDAP://$fqdnDC/$psoContainerDN" $searcher = New-Object System.DirectoryServices.DirectorySearcher($searchRoot) $searcher.Filter = "(objectClass=msDS-PasswordSettings)" $searcher.SearchScope = "Subtree" $searcher.PageSize = 1000 $propertyList = "distinguishedName","name","msDS-MaximumPasswordAge","msDS-MinimumPasswordAge","msDS-MinimumPasswordLength","msDS-PasswordComplexityEnabled","msDS-PasswordHistoryLength" ForEach ($property in $propertyList){ $searcher.PropertiesToLoad.Add($property) | Out-Null } $results = $NULL $results = $searcher.FindAll() ### For Every Discovered PSO Get Its Properties (REMEMBER: The Account Running This Script Must Have Allow:Read Permissions On The PSO Container Itself And Sub Objects If ($results -ne $null) { $results | %{ $pwdPolicyPSOInDomainObj = "" | Select DN,name,MaxPwdAge,MinPwdAge,MinPwdLength,PwdComplexity,PwdHistoryLength $pwdPolicyPSOInDomainObj.DN = $_.Properties.distinguishedname[0] $psoName = $_.Properties.name[0] $pwdPolicyPSOInDomainObj.name = $($psoName + " (" + $fqdnADdomain + ")") Logging "" Logging " --> Name............: $psoName" $psoMaxPwdAge = [System.TimeSpan]::FromTicks([System.Math]::ABS($_.Properties."msds-maximumpasswordage"[0])).Days $pwdPolicyPSOInDomainObj.MaxPwdAge = $psoMaxPwdAge Logging " --> Max Pwd Age.....: $psoMaxPwdAge" $psoMinPwdAge = [System.TimeSpan]::FromTicks([System.Math]::ABS($_.Properties."msds-minimumpasswordage"[0])).Days $pwdPolicyPSOInDomainObj.MinPwdAge = $psoMinPwdAge Logging " --> Min Pwd Age.....: $psoMinPwdAge" $psoMinPwdLength = $_.Properties."msds-minimumpasswordlength"[0] $pwdPolicyPSOInDomainObj.MinPwdLength = $psoMinPwdLength Logging " --> Min Pwd Length..: $psoMinPwdLength" If ($_.Properties."msds-passwordcomplexityenabled"[0]) { $pwdPolicyPSOInDomainObj.PwdComplexity = "TRUE" Logging " --> Pwd Complexity..: TRUE" } Else { $pwdPolicyPSOInDomainObj.PwdComplexity = "FALSE" Logging " --> Pwd Complexity..: FALSE" } $psoPwdHistoryLength = $_.Properties."msds-passwordhistorylength"[0] $pwdPolicyPSOInDomainObj.PwdHistoryLength = $psoPwdHistoryLength Logging " --> Pwd Complexity..: $psoPwdHistoryLength" $pwdPolicyInDomain += $pwdPolicyPSOInDomainObj } } $searcher = $NULL $results = $NULL } ### Get The Password Policy Settings From The Default Domain GPO Which Are Also Registered On The AD Domain NC Head Logging "" Logging " --> Default Domain GPO Password Settings" ### Setup The LDAP Query To Get The Password Policy Settings From The Default Domain GPO And Execute The Query $searchRoot = $NULL $searchRoot = [ADSI]"LDAP://$fqdnDC/$defaultNC" $searcher = New-Object System.DirectoryServices.DirectorySearcher($searchRoot) $searcher.SearchScope = "Base" $propertyList = "maxPwdAge","minPwdAge","minPwdLength","pwdProperties","pwdHistoryLength" ForEach ($property in $propertyList){ $searcher.PropertiesToLoad.Add($property) | Out-Null } $results = $NULL $results = $searcher.FindOne() ### Get The Properties And Process Them $results | %{ $pwdPolicyGPOInDomainObj = "" | Select DN,name,MaxPwdAge,MinPwdAge,MinPwdLength,PwdComplexity,PwdHistoryLength $pwdPolicyGPOInDomainObj.DN = $defaultNC[0] $gpoName = "DefaultDomainGPO (" + $fqdnADdomain + ")" $pwdPolicyGPOInDomainObj.name = $gpoName Logging "" Logging " --> Name............: $gpoName" $gpoMaxPwdAge = [System.TimeSpan]::FromTicks([System.Math]::ABS($_.Properties.maxpwdage[0])).Days $pwdPolicyGPOInDomainObj.MaxPwdAge = $gpoMaxPwdAge Logging " --> Max Pwd Age.....: $gpoMaxPwdAge" $gpoMinPwdAge = [System.TimeSpan]::FromTicks([System.Math]::ABS($_.Properties.minpwdage[0])).Days $pwdPolicyGPOInDomainObj.MinPwdAge = $gpoMinPwdAge Logging " --> Min Pwd Age.....: $gpoMinPwdAge" $psoMinPwdLength = $_.Properties.minpwdlength[0] $pwdPolicyGPOInDomainObj.MinPwdLength = $psoMinPwdLength Logging " --> Min Pwd Length..: $psoMinPwdLength" If (($results.Properties.pwdproperties[0] -band 0x1) -Eq 1) { $pwdPolicyGPOInDomainObj.PwdComplexity = "TRUE" Logging " --> Pwd Complexity..: TRUE" } Else { $pwdPolicyGPOInDomainObj.PwdComplexity = "FALSE" Logging " --> Pwd Complexity..: FALSE" } $gpoPwdHistoryLength = $_.Properties.pwdhistorylength[0] $pwdPolicyGPOInDomainObj.PwdHistoryLength = $gpoPwdHistoryLength Logging " --> Pwd Complexity..: $gpoPwdHistoryLength" $pwdPolicyInDomain += $pwdPolicyGPOInDomainObj } $searcher = $NULL $results = $NULL Logging "" Logging " --> Search Bases In AD Domain" ### Processing Each Configured Search Base Within An AD Domain In The XML Config File $searchBases = $_.searchBase $searchBases | %{ $searchBase = $_."#text" $languageForUser = $_.language $mailSubjectForUser = ($htmlBodyFiles | ?{$_.language -eq $languageForUser}).mailSubject $htmlBodyFileForUser = ($htmlBodyFiles | ?{$_.language -eq $languageForUser}).fullPath Logging "" ### Let's Make Sure The Configured Search Base Does Exist $searchBaseStatus = $NULL $searchBaseStatus = CheckDNExistence $fqdnDC $searchBase Logging " --> Search Base..........: $searchBase (Status: $searchBaseStatus)" ### If The Search Base Does Exist Then Continue If ($searchBaseStatus -eq "SUCCESS") { ### Setup The LDAP Query To Get The User Objects And Execute The Query $searchRoot = $NULL $searchRoot = [ADSI]"LDAP://$fqdnDC/$searchBase" $searcher = New-Object System.DirectoryServices.DirectorySearcher($searchRoot) $searcher.Filter = "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(userAccountControl:1.2.840.113556.1.4.803:=65536))(mail=*)(!(pwdLastSet=0)))" $searcher.SearchScope = "Subtree" $searcher.PageSize = 1000 $propertyList = "distinguishedName","givenName","sn","displayName","mail","pwdLastSet","msDS-UserPasswordExpiryTimeComputed","accountExpires","msDS-ResultantPSO" ForEach ($property in $propertyList){ $searcher.PropertiesToLoad.Add($property) | Out-Null } $results = $NULL $results = $searcher.FindAll() $userCountInSearchBase = ($results | Measure-Object).Count Logging " --> Queried User Count...: $userCountInSearchBase" Logging " --> Specified Language...: $languageForUser" ### Get The Properties And Process Them $results | %{ $listOfQueriedUsersObj = "" | Select "FQDN AD Domain",DN,"Given Name","Last Name","Display Name","E-Mail Address","PWD Last Set","PWD Expire Date","Days Until PWD Expiry","Account Expiry Date","Days Until Account Expiry","Effective PWD Policy","Language","Mail Subject","HTML Body File" $listOfQueriedUsersObj."FQDN AD Domain" = $fqdnADdomain $listOfQueriedUsersObj.DN = $_.Properties.distinguishedname[0] If ($_.Properties.givenname -ne $null) { $listOfQueriedUsersObj."Given Name" = $_.Properties.givenname[0] } Else { $listOfQueriedUsersObj."Given Name" = $null } If ($_.Properties.sn -ne $null) { $listOfQueriedUsersObj."Last Name" = $_.Properties.sn[0] } Else { $listOfQueriedUsersObj."Last Name" = $null } If ($_.Properties.displayname -ne $null) { $listOfQueriedUsersObj."Display Name" = $_.Properties.displayname[0] } Else { $listOfQueriedUsersObj."Display Name" = $null } $listOfQueriedUsersObj."E-Mail Address" = $_.Properties.mail[0] $adUserPwdLastSet = Get-Date -Date ([DateTime]::FromFileTime([Int64]::Parse($_.Properties.pwdlastset[0]))) -Format $formatDateTime $listOfQueriedUsersObj."PWD Last Set" = $adUserPwdLastSet $adUserPwdExpires = Get-Date -Date ([DateTime]::FromFileTime([Int64]::Parse($_.Properties."msds-userpasswordexpirytimecomputed"[0]))) -Format $formatDateTime $listOfQueriedUsersObj."PWD Expire Date" = $adUserPwdExpires $timeDiffPwdExpiryInDays = (New-TimeSpan -Start $execStartDateTime -end $adUserPwdExpires).TotalDays $listOfQueriedUsersObj."Days Until PWD Expiry" = $timeDiffPwdExpiryInDays $adUserAccountExpires = $_.Properties.accountexpires[0] ### If An Account Is Configured With Never Expires, Then Assign An Insane End Date To Be Able To Perform Calculations If ($adUserAccountExpires -eq 9223372036854775807 -Or $adUserAccountExpires -eq 0) { $adUserAccountExpires = Get-Date "9999-12-31 23:59:59" -format $formatDateTime } Else { $adUserAccountExpires = Get-Date -Date ([DateTime]::FromFileTime([Int64]::Parse($adUserAccountExpires))) -Format $formatDateTime } $listOfQueriedUsersObj."Account Expiry Date" = $adUserAccountExpires $timeDiffAccountExpiryInDays = (New-TimeSpan -Start $execStartDateTime -end $adUserAccountExpires).TotalDays $listOfQueriedUsersObj."Days Until Account Expiry" = $timeDiffAccountExpiryInDays If ($_.Properties."msds-resultantpso" -ne $null) { $effectivePWDPolicyDN = $_.Properties."msds-resultantpso"[0] } Else { $effectivePWDPolicyDN = $defaultNC } $effectivePWDPolicyName = ($pwdPolicyInDomain | ?{$_.DN -eq $effectivePWDPolicyDN}).name $listOfQueriedUsersObj."Effective PWD Policy" = $effectivePWDPolicyName $listOfQueriedUsersObj."Language" = $languageForUser $listOfQueriedUsersObj."Mail Subject" = $mailSubjectForUser $listOfQueriedUsersObj."HTML Body File" = $htmlBodyFileForUser $listOfQueriedUsers += $listOfQueriedUsersObj } $searcher = $NULL $results = $NULL } Else { ### If The Search Base Does NOT Exist Then Skip That Search Base And Send Mail About It Logging " --> SKIPPED DUE TO ERROR!" Send-MailMessage -SmtpServer $smtpServer -From $mailFromSender -To $toSMTPAddressInTestMode -Priority High -Subject "Error With Defined SearchBase!" -Body "The Search Base '$searchBase' Does Not Exist!" -BodyAsHtml } } } } Logging "" Logging "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ### Now Having The List Of Queried Users, Determine Which Of Those Users Require E-mail Notification Based Upon The Configured Warning Periods Logging "" Logging "Processing Warning Periods..." ### From The List Of Queried Users, Get Those Users For Which The AD User Account Has Not Expired Yet $listOfUsersWithNonExpiredAccounts = $listOfQueriedUsers | ?{$_."Days Until Account Expiry" -gt 0} ### Creating An Empty Array For Users That Will Be Notified! $listOfUsersWithExpiringPWDToNotify = @() ### Process Every Configured Warning Period. Make Sure In The XML NOT To Have Overlapping Periods! $daysBeforeWarn | %{ $max = $_.max $min = $_.min Logging "" Logging "** Period: Max: $max Days | Min: $min Days **" $listOfUsersWithinWarningPeriod = $listOfUsersWithNonExpiredAccounts | ?{$_."Days Until PWD Expiry" -lt $max -And $_."Days Until PWD Expiry" -gt $min} $userCountInWarningPeriod = ($listOfUsersWithinWarningPeriod | Measure-Object).Count Logging " --> User Count Within Warning Period...: $userCountInWarningPeriod" $listOfUsersWithExpiringPWDToNotify += $listOfUsersWithinWarningPeriod } ### If If Was Configured To Export The List Of Users That Will Be Notified, Than Do So! If ($exportToCSV.ToUpper() -eq "ON") { $listOfUsersWithExpiringPWDToNotify | Export-Csv -Path $fullPathToCSVFile -NoTypeInformation } #Logging "" #Logging "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ### Show On Screen The List Of Queried Users Including Some Details #Logging "" #Logging "List Of Queried Users..." #$listOfUsersWithNonExpiredAccounts | FT "FQDN AD Domain",DN,"Given Name","Last Name","Display Name","E-Mail Address","PWD Last Set","PWD Expire Date","Days Until PWD Expiry","Account Expiry Date","Days Until Account Expiry","Effective PWD Policy","Language","Mail Subject","HTML Body File" -Autosize #$listOfUsersWithNonExpiredAccounts | FT "FQDN AD Domain","Display Name","E-Mail Address","PWD Expire Date","Days Until PWD Expiry","Effective PWD Policy","Language" -Autosize #$userCountQueried = ($listOfUsersWithNonExpiredAccounts | Measure-Object).Count #Logging "--> User Count To Be Queried....: $userCountQueried" Logging "" Logging "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ### Show On Screen The List Of Users That Will Be Notified, Including Some Details Logging "" Logging "List Of Notified Users..." #$listOfUsersWithExpiringPWDToNotify | FT "FQDN AD Domain",DN,"Given Name","Last Name","Display Name","E-Mail Address","PWD Last Set","PWD Expire Date","Days Until PWD Expiry","Account Expiry Date","Days Until Account Expiry","Effective PWD Policy","Language","Mail Subject","HTML Body File" -Autosize $listOfUsersWithExpiringPWDToNotify | FT "FQDN AD Domain","Display Name","E-Mail Address","PWD Expire Date","Days Until PWD Expiry","Effective PWD Policy","Language" -Autosize $userCountNotified = ($listOfUsersWithExpiringPWDToNotify | Measure-Object).Count Logging "--> User Count To Be Notified...: $userCountNotified" ### If The FORCE Parameter Was NOT Specified With TRUE Then DO NOT Send Any E-Mail If ($executionMode.ToUpper() -eq "TEST (NO MAILINGS)") { Logging "" Logging " --> No Notifications Have Been Send!" } Else { ### When Running In DEV Mode Execute This Part If ($executionMode.ToUpper() -eq "DEV") { Logging "" Logging "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" Logging "" Logging "Displaying Information Of The Development User..." ### Get The Current AD Domain $ThisADDomain = [DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() $fqdnThisADDomain = $ThisADDomain.Name ### Discover An RWDC For That Current AD Domain $fqdnDC = DiscoverRWDC $fqdnThisADDomain ### Setup The LDAP Query To Get The Information Of The User And Execute The Query $RootDSE = [ADSI]"LDAP://$fqdnDC/RootDSE" $defaultNC = $RootDSE.defaultNamingContext $searchRoot = $NULL $searchRoot = [ADSI]"LDAP://$fqdnDC/$defaultNC" $searcher = New-Object System.DirectoryServices.DirectorySearcher($searchRoot) $searcher.Filter = "(&(objectCategory=person)(objectClass=user)(|(proxyAddresses=smtp:$toSMTPAddressInTestMode)(proxyAddresses=SMTP:$toSMTPAddressInTestMode)))" $searcher.SearchScope = "Subtree" $searcher.PageSize = 1000 $propertyList = "distinguishedName","givenName","sn","displayName","pwdLastSet","msDS-UserPasswordExpiryTimeComputed","msDS-ResultantPSO" ForEach ($property in $propertyList){ $searcher.PropertiesToLoad.Add($property) | Out-Null } $results = $NULL $results = $searcher.FindOne() ### Get The Properties Of The User If ($results.Properties.givenname -ne $null) { $adUserGivenName = $results.Properties.givenname[0] } Else { $adUserGivenName = "NO-VALUE" } If ($results.Properties.sn -ne $null) { $adUserSn = $results.Properties.sn[0] } Else { $adUserSn = "NO-VALUE" } If ($results.Properties.displayname -ne $null) { $adUserDisplayName = $results.Properties.displayname[0] } Else { $adUserDisplayName = "NO-VALUE" } $adUserPwdLastSet = Get-Date -Date ([DateTime]::FromFileTime([Int64]::Parse($results.Properties.pwdlastset[0]))) -Format $formatDateTime $adUserPwdExpires = $results.Properties."msds-userpasswordexpirytimecomputed"[0] ### If A Password Is Configured With Never Expires, Then Assign An Insane End Date To Be Able To Perform Calculations If ($adUserPwdExpires -eq 9223372036854775807 -Or $adUserAccountExpires -eq 0) { $adUserPwdExpires = Get-Date "9999-12-31 23:59:59" -format $formatDateTime } Else { $adUserPwdExpires = Get-Date -Date ([DateTime]::FromFileTime([Int64]::Parse($adUserPwdExpires))) -Format $formatDateTime } $timeDiffPwdExpiryInDays = (New-TimeSpan -Start $execStartDateTime -end $adUserPwdExpires).TotalDays If ($results.Properties."msds-resultantpso" -ne $null) { $effectivePWDPolicyDN = $results.Properties."msds-resultantpso"[0] } Else { $effectivePWDPolicyDN = $defaultNC } $effectivePWDPolicyOnUser = $pwdPolicyInDomain | ?{$_.DN -eq $effectivePWDPolicyDN} ### Get The Settings Of The Effective PWD Policy On The User $policyPWDName = $effectivePWDPolicyOnUser.Name $policyPWDMinLength = $effectivePWDPolicyOnUser.MinPwdLength $policyPWDMinAge = $effectivePWDPolicyOnUser.MinPwdAge $policyPWDMaxAge = $effectivePWDPolicyOnUser.MaxPwdAge $policyPWDHistory = $effectivePWDPolicyOnUser.PwdHistoryLength $policyPWDComplexity = $effectivePWDPolicyOnUser.PwdComplexity ### Get The Content Of The HTML File That Will Be Used For The E-Mails $languageForUser = "default" $mailSubjectForUser = ($htmlBodyFiles | ?{$_.language -eq $languageForUser}).mailSubject $htmlBodyFileForUser = ($htmlBodyFiles | ?{$_.language -eq $languageForUser}).fullPath $mailBodyForUser = Get-Content $htmlBodyFileForUser Logging "" Logging "Display Name................: $adUserDisplayName" Logging "First Name..................: $adUserGivenName" Logging "Last Name...................: $adUserSn" Logging "PWD Last Set................: $adUserPwdLastSet" Logging "PWD Expiry Date.............: $adUserPwdExpires" Logging "Days Until PWD Expiry Date..: $timeDiffPwdExpiryInDays ($([math]::Round($timeDiffPwdExpiryInDays)))" Logging "Effective PWD Policy Name...: $policyPWDName" Logging "Effective PWD Min Length....: $policyPWDMinLength" Logging "Effective PWD Min Age.......: $policyPWDMinAge" Logging "Effective PWD Max Age.......: $policyPWDMaxAge" Logging "Effective PWD History.......: $policyPWDHistory" Logging "Effective PWD Complexity....: $policyPWDComplexity" Logging "Language....................: $languageForUser" Logging "Mail Subject................: $mailSubjectForUser" Logging "HTML Body File..............: $htmlBodyFileForUser" ### Replace Any Variables In The SUBJECT With The Actual Values $mailSubject = $mailSubjectForUser -replace "FIRST_NAME",$adUserGivenName $mailSubject = $mailSubject -replace "LAST_NAME",$adUserSn $mailSubject = $mailSubject -replace "DISPLAY_NAME",$adUserDisplayName $mailSubject = $mailSubject -replace "FQDN_DOMAIN",$fqdnThisADDomain $mailSubject = $mailSubject -replace "PWD_EXPIRY_DATE",$adUserPwdExpires $mailSubject = $mailSubject -replace "PWD_EXPIRE_IN_NUM_DAYS",[math]::Round($timeDiffPwdExpiryInDays) $mailSubject = $mailSubject -replace "PWD_MIN_LENGTH",$policyPWDMinLength $mailSubject = $mailSubject -replace "PWD_MIN_AGE",$policyPWDMinAge $mailSubject = $mailSubject -replace "PWD_MAX_AGE",$policyPWDMaxAge $mailSubject = $mailSubject -replace "PWD_HISTORY",$policyPWDHistory $mailSubject = $mailSubject -replace "PWD_COMPLEX",$policyPWDComplexity $mailSubject = $mailSubject -replace "PWD_CHANGE_RESET_URL",$pwdChangeOrResetURL ### Replace Any Variables In The BODY With The Actual Values $mailBody = $mailBodyForUser -replace "FIRST_NAME",$adUserGivenName $mailBody = $mailBody -replace "LAST_NAME",$adUserSn $mailBody = $mailBody -replace "DISPLAY_NAME",$adUserDisplayName $mailBody = $mailBody -replace "FQDN_DOMAIN",$fqdnThisADDomain $mailBody = $mailBody -replace "PWD_EXPIRY_DATE",$adUserPwdExpires $mailBody = $mailBody -replace "PWD_EXPIRE_IN_NUM_DAYS",[math]::Round($timeDiffPwdExpiryInDays) $mailBody = $mailBody -replace "PWD_MIN_LENGTH",$policyPWDMinLength $mailBody = $mailBody -replace "PWD_MIN_AGE",$policyPWDMinAge $mailBody = $mailBody -replace "PWD_MAX_AGE",$policyPWDMaxAge $mailBody = $mailBody -replace "PWD_HISTORY",$policyPWDHistory $mailBody = $mailBody -replace "PWD_COMPLEX",$policyPWDComplexity $mailBody = $mailBody -replace "PWD_CHANGE_RESET_URL",$pwdChangeOrResetURL ### Send A Notification E-Mail About The Expiring Password Send-MailMessage -SmtpServer $smtpServer -From $mailFromSender -To $toSMTPAddressInTestMode -Priority $mailPriority -Subject $mailSubject -Body $($mailBody | Out-String) -BodyAsHtml Logging "" Logging " --> Notifying '$adUserDisplayName' by sending an e-mail to '$toSMTPAddressInTestMode'" } Else { ### When Running In TEST Or PROD Mode Execute This Part ### Process Any User With Expiring Password And Send E-Mail Notification About The Expiring Password $listOfUsersWithExpiringPWDToNotify | %{ ### If The FORCE Parameter Was Specified With TRUE Then Send E-Mail Based On The Configured Execution Mode If ($executionMode.ToUpper() -eq "TEST") { ### For All Users Send E-Mail Notifications To The Configured Admin Mail Address $mailToRecipient = $toSMTPAddressInTestMode } If ($executionMode.ToUpper() -eq "PROD") { ### For All Users Send E-Mail Notifications To The E-Mail Address Of Each User $mailToRecipient = $_."E-Mail Address" } ### Get The Display Name Of The User $displayNameUser = $_."Display Name" ### Get The Effective PWD Policy On The User $effectivePWDPolicyNameOnUser = $_."Effective PWD Policy" $effectivePWDPolicyOnUser = $pwdPolicyInDomain | ?{$_.Name -eq $effectivePWDPolicyNameOnUser} ### Get The Settings Of The Effective PWD Policy On The User $policyPWDMinLength = $effectivePWDPolicyOnUser.MinPwdLength $policyPWDMinAge = $effectivePWDPolicyOnUser.MinPwdAge $policyPWDMaxAge = $effectivePWDPolicyOnUser.MaxPwdAge $policyPWDHistory = $effectivePWDPolicyOnUser.PwdHistoryLength $policyPWDComplexity = $effectivePWDPolicyOnUser.PwdComplexity ### Get The Content Of The HTML File That Will Be Used For The E-Mails $languageForUser = $_.Language $mailSubjectForUser = ($htmlBodyFiles | ?{$_.language -eq $languageForUser}).mailSubject $htmlBodyFileForUser = ($htmlBodyFiles | ?{$_.language -eq $languageForUser}).fullPath $mailBodyForUser = Get-Content $htmlBodyFileForUser ### Replace Any Variables In The SUBJECT With The Actual Values $mailSubject = $mailSubjectForUser -replace "FIRST_NAME",$_."Given Name" $mailSubject = $mailSubject -replace "LAST_NAME",$_."Last Name" $mailSubject = $mailSubject -replace "DISPLAY_NAME",$_."Display Name" $mailSubject = $mailSubject -replace "FQDN_DOMAIN",$_."FQDN AD Domain" $mailSubject = $mailSubject -replace "PWD_EXPIRY_DATE",$_."PWD Expire Date" $mailSubject = $mailSubject -replace "PWD_EXPIRE_IN_NUM_DAYS",[math]::Round($_."Days Until PWD Expiry") $mailSubject = $mailSubject -replace "PWD_MIN_LENGTH",$policyPWDMinLength $mailSubject = $mailSubject -replace "PWD_MIN_AGE",$policyPWDMinAge $mailSubject = $mailSubject -replace "PWD_MAX_AGE",$policyPWDMaxAge $mailSubject = $mailSubject -replace "PWD_HISTORY",$policyPWDHistory $mailSubject = $mailSubject -replace "PWD_COMPLEX",$policyPWDComplexity $mailSubject = $mailSubject -replace "PWD_CHANGE_RESET_URL",$pwdChangeOrResetURL ### Replace Any Variables In The BODY With The Actual Values $mailBody = $mailBodyForUser -replace "FIRST_NAME",$_."Given Name" $mailBody = $mailBody -replace "LAST_NAME",$_."Last Name" $mailBody = $mailBody -replace "DISPLAY_NAME",$_."Display Name" $mailBody = $mailBody -replace "FQDN_DOMAIN",$_."FQDN AD Domain" $mailBody = $mailBody -replace "PWD_EXPIRY_DATE",$_."PWD Expire Date" $mailBody = $mailBody -replace "PWD_EXPIRE_IN_NUM_DAYS",[math]::Round($_."Days Until PWD Expiry") $mailBody = $mailBody -replace "PWD_MIN_LENGTH",$policyPWDMinLength $mailBody = $mailBody -replace "PWD_MIN_AGE",$policyPWDMinAge $mailBody = $mailBody -replace "PWD_MAX_AGE",$policyPWDMaxAge $mailBody = $mailBody -replace "PWD_HISTORY",$policyPWDHistory $mailBody = $mailBody -replace "PWD_COMPLEX",$policyPWDComplexity $mailBody = $mailBody -replace "PWD_CHANGE_RESET_URL",$pwdChangeOrResetURL ### Send A Notification E-Mail About The Expiring Password Send-MailMessage -SmtpServer $smtpServer -From $mailFromSender -To $mailToRecipient -Priority $mailPriority -Subject $mailSubject -Body $($mailBody | Out-String) -BodyAsHtml Logging "" Logging " --> Notifying '$displayNameUser' by sending an e-mail to '$mailToRecipient'" } } } Logging "" Logging "#######################################################################################"

You can also download everything from HERE

I HAVE NOT TESTED EVERY POSSIBLE SCENARIO! Please provide feedback through the comments section OR you the contact page

DISCLAIMER (READ THIS!):

  • I wrote this script, therefore I own it. Anyone asking money for it, should NOT be doing that and is basically ripping you off!
  • The script is freeware, you are free to use it and distribute it, but always refer to this website (https://jorgequestforknowledge.wordpress.com/) as the location where you got it.
  • This script is furnished "AS IS". No warranty is expressed or implied!
  • I have NOT tested it in every scenario nor have I tested it against every Windows and/or AD version
  • Always test first in lab environment to see if it meets your needs!
  • Use this script at your own risk!
  • I do not warrant this script to be fit for any purpose, use or environment!
  • I have tried to check everything that needed to be checked, but I do not guarantee the script does not have bugs!
  • I do not guarantee the script will not damage or destroy your system(s), environment or whatever!
  • I do not accept liability in any way if you screw up, use the script wrong or in any other way where damage is caused to your environment/systems!
  • If you do not accept these terms do not use the script in any way and delete it immediately!

Cheers,

Jorge

———————————————————————————————

* This posting is provided "AS IS" with no warranties and confers no rights!

* Always evaluate/test yourself before using/implementing this!

* DISCLAIMER: https://jorgequestforknowledge.wordpress.com/disclaimer/

———————————————————————————————

############### Jorge’s Quest For Knowledge #############

######### http://JorgeQuestForKnowledge.wordpress.com/ ########

———————————————————————————————

Posted in Active Directory Domain Services (ADDS), Fine Grained Password Policies, Password Expiration Notification, PowerShell, Tooling/Scripting | Leave a Comment »

(2016-05-08) Copying OUs, Users, Groups And GPOs From One AD To A Different AD

Posted by Jorge on 2016-05-08


Almost 11 years ago I wrote the following blog posts about creating a test environment.

Those blog posts mention the use of the legacy GPMC scripts to copy OUs, user, groups and GPOs from one AD to another AD.

Ian Farr, working at Microsoft, wrote a PowerShell module to do exactly the same, copying OUs, user, groups and GPOs from one AD to another AD. Wanna try it out?

Go and get the PowerShell module from the script gallery here

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in PowerShell, Tooling/Scripting | Leave a Comment »

(2016-05-07) Azure AD Connect – Identifying Objects In AD And In Azure AD (Part 6)

Posted by Jorge on 2016-05-07


Part 5 can be found here

The next object we need to take into account is the group object for inbound synchronization

Clone and disable the synchronization rule called “In from AD – Group Join”.

While on the “Description” node:

  • Assign the name “In from AD – Group Join (Custom – <AD Forest FQDN>)” to the clone
  • Assign the description “Cloned Sync Rule From ”In from AD – Group Join”” to the clone
  • Assign the precedence of 80
  • With regards to configuring a tag, it apparently is not possible to do that right after cloning a synchronization rule. Finish the configuration of the rule, save it, export it to a PowerShell script, edit the script and add a tag and save the script, and rerun the script to update the cloned synchronization rule

Apply the exact configuration as explained above for the cloned rule called “In from AD – User Join (Custom – <AD Forest FQDN>)” (part 4 figure 2 or 3, 4).

Clone and disable the synchronization rule called “In from AD – Group Common”.

While on the “Description” node:

  • Assign the name “In from AD – Group Common (Custom – <AD Forest FQDN>)” to the clone
  • Assign the description “Cloned Sync Rule From ‘In from AD – Group Common’” to the clone
  • Assign the precedence of 84
  • With regards to configuring a tag, it apparently is not possible to do that right after cloning a synchronization rule. Finish the configuration of the rule, save it, export it to a PowerShell script, edit the script and add a tag and save the script, and rerun the script to update the cloned synchronization rule

Click on the “Transformations” node:

Configure the yellow marked part to make sure the attribute listed is populated in the MV

If you are using a Unicode String Attribute, configure the transformation rules as followed (only taking the yellow marked parts into account)

image

Figure 1: Transformation Rules For The Cloned Synchronization Rule “In from AD – Group Common (Custom – <AD Forest FQDN>)””. When Using A Unicode String Attribute

For BOTH the target attribute “sourceAnchor” and the target attribute “extensionAtttribute15”, the source is:

IIF(IsPresent([extensionAttribute15]),[extensionAttribute15],ConvertToBase64([objectGUID]))

The expression can be better seen as:

IIF(

    IsPresent([extensionAttribute15]),

    [extensionAttribute15],

    ConvertToBase64([objectGUID])

)

If you are using a Octet String Attribute, configure the transformation rules as followed (only taking the yellow marked parts into account)

image

Figure 2: Transformation Rules For The Cloned Synchronization Rule “In from AD – Group Common (Custom – <AD Forest FQDN>)”. When Using An Octet String Attribute

For the target attribute “sourceAnchor”, the source is:

IIF(IsPresent([mS-DS-ConsistencyGuid]),ConvertToBase64([mS-DS-ConsistencyGuid]),ConvertToBase64([objectGUID]))

The expression can be better seen as:

IIF(

    IsPresent([mS-DS-ConsistencyGuid]),

    ConvertToBase64([mS-DS-ConsistencyGuid]),

    ConvertToBase64([objectGUID])

)

For the target attribute “mS-DS-ConsistencyGuid”, the source is:

IIF(IsPresent([mS-DS-ConsistencyGuid]),[mS-DS-ConsistencyGuid],[objectGUID])

The expression can be better seen as:

IIF(

    IsPresent([mS-DS-ConsistencyGuid]),

    [mS-DS-ConsistencyGuid],

    [objectGUID]

)

The first yellow marked transformation (figure 17 and 18) will flow the value from either “extensionAttribute15” or “mS-DS-ConsistencyGuid” (whichever you are using) to the “sourceAnchor” if a value already exists. If not, the objectGUID in base64 format will be used as the value.

The second yellow marked transformation (figure 17 and 18) will flow the value from either “extensionAttribute15” or “mS-DS-ConsistencyGuid” (whichever you are using) to respectively the “extensionAttribute15” or “mS-DS-ConsistencyGuid” (whichever you are using) if a value already exists. If not, the objectGUID in base64 format will be used as the value.

This synchronization rule is now fully configured and therefore you can save it.

Now we need to create a brand new synchronization rule for the “group” object so that the immutable ID value is written back to AD (outbound synchronization) in a manageable attribute of the user object.

REMARK: to AAD Connect to be able to write back the value, the account used in the AD connector must have Allow:Read/Write permissions on the group objects for which it is writing back a value into the chosen manageable attribute that will provide the immutable ID value. To configure the correct permissions you can use the following command:

DSACLS "\\<FQDN RWDC>\<distinguishedName of OU>" /G "<account used in the AD connector>:RPWP;<chosen immutable ID attribute>;group" /I:S

Create/add a new synchronization rule.

While on the “Description” node:

  • Assign the name “Out to AD – Group Common (Custom – <AD Forest FQDN>)”
  • Assign the description “New Sync Rule”
  • Assign as connected system the AD forest
  • Assign as connected system object type: “group”
  • Assign as metaverse object type: “group”
  • Assign as link type: “Join”
  • Assign the precedence of 270
  • Assign the tag “<companyname>.OuttoADGroupCommonCustom.001”
  • Keep UNchecked “Enable Password Sync”
  • Keep UNchecked “Disabled”

For the rest of it, apply the exact configuration as explained above for the new rule called “Out to AD – User Common (Custom – <AD Forest FQDN>)” (part 4 figure 7 or 8, 9, 10 or 11).

This synchronization rule becomes active on a group object in the MV after the listed attribute (part 4 figure 7 or 8) has been populated with a value

REMARK: to AAD Connect to be able to write back the value, the account used in the AD connector must have Allow:Read/Write permissions on the group objects for which it is writing back a value into the chosen manageable attribute that will provide the immutable ID value. To configure the correct permissions you can use the following command:

DSACLS "\\<FQDN RWDC>\<distinguishedName of OU>" /G "<account used in the AD connector>:RPWP;<chosen immutable ID attribute>;group" /I:S

Now to make sure you have a backup configuration of your AAD Connect installation, execute the following command:

Get-ADSyncServerConfiguration <some folder>

Now that everything is ready, you need to enable synchronization. You can do that through:

Set-ADSyncScheduler -SyncCycleEnabled $TRUE

If you want to kick synchronization to start right away, execute the following command:

Start-ADSyncSyncCycle -PolicyType Initial

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Azure AD Connect, Windows Azure Active Directory | 2 Comments »

(2016-05-07) Azure AD Connect – Identifying Objects In AD And In Azure AD (Part 5)

Posted by Jorge on 2016-05-07


Part 4 can be found here

The next object we need to take into account is the InetOrgPerson object for inbound synchronization

Clone and disable the synchronization rule called “In from AD – InetOrgPerson Join”.

While on the “Description” node:

  • Assign the name “In from AD – InetOrgPerson Join (Custom – <AD Forest FQDN>)” to the clone
  • Assign the description “Cloned Sync Rule From ‘In from AD – InetOrgPerson Join’” to the clone
  • Assign the precedence of 70
  • With regards to configuring a tag, it apparently is not possible to do that right after cloning a synchronization rule. Finish the configuration of the rule, save it, export it to a PowerShell script, edit the script and add a tag and save the script, and rerun the script to update the cloned synchronization rule

Apply the exact configuration as explained above for the cloned rule called “In from AD – User Join (Custom – <AD Forest FQDN>)” (part 4 figure 2 or 3, 4).

Next clone and disable the synchronization rule called “In from AD – InetOrgPerson AccountEnabled”.

While on the “Description” node:

  • Assign the name “In from AD – InetOrgPerson AccountEnabled (Custom – <AD Forest FQDN>)” to the clone
  • Assign the description “Cloned Sync Rule From ‘In from AD – InetOrgPerson AccountEnabled’” to the clone
  • Assign the precedence of 72
  • With regards to configuring a tag, it apparently is not possible to do that right after cloning a synchronization rule. Finish the configuration of the rule, save it, export it to a PowerShell script, edit the script and add a tag and save the script, and rerun the script to update the cloned synchronization rule

Apply the exact configuration as explained above for the cloned rule called “In from AD – User AccountEnabled (Custom – <AD Forest FQDN>)”.

Now we need to create a brand new synchronization rule for the “person” object so that the immutable ID value is written back to AD (outbound synchronization) in a manageable attribute of the iNetOrgPerson object.

Create/add a new synchronization rule.

While on the “Description” node:

  • Assign the name “Out to AD – iNetOrgPerson Common (Custom – <AD Forest FQDN>)”
  • Assign the description “New Sync Rule”
  • Assign as connected system the AD forest
  • Assign as connected system object type: “iNetOrgPerson”
  • Assign as metaverse object type: “person”
  • Assign as link type: “Join”
  • Assign the precedence of 260
  • Assign the tag “<companyname>.OuttoADiNetOrgPersonCommonCustom.001”
  • Keep UNchecked “Enable Password Sync”
  • Keep UNchecked “Disabled”

For the rest of it, apply the exact configuration as explained above for the new rule called “Out to AD – User Common (Custom – <AD Forest FQDN>)” (part 4 figure 7 or 8, 9, 10 or 11).

This synchronization rule becomes active on a person object in the MV after the listed attribute (part 4 figure 7 or 8) has been populated with a value

REMARK: to AAD Connect to be able to write back the value, the account used in the AD connector must have Allow:Read/Write permissions on the iNetOrgPerson objects for which it is writing back a value into the chosen manageable attribute that will provide the immutable ID value. To configure the correct permissions you can use the following command:

DSACLS "\\<FQDN RWDC>\<distinguishedName of OU>" /G "<account used in the AD connector>:RPWP;<chosen immutable ID attribute>;iNetOrgPerson" /I:S

Continue with part 6 here

Cheers,
Jorge
———————————————————————————————
* This posting is provided "AS IS" with no warranties and confers no rights!
* Always evaluate/test yourself before using/implementing this!
* DISCLAIMER:
https://jorgequestforknowledge.wordpress.com/disclaimer/
———————————————————————————————
############### Jorge’s Quest For Knowledge #############
#########
http://JorgeQuestForKnowledge.wordpress.com/ ########
———————————————————————————————

Posted in Azure AD Connect, Windows Azure Active Directory | 2 Comments »

 
%d bloggers like this: