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!)

Archive for the ‘Active Directory Domain Services (ADDS)’ Category

(2016-07-20) Installing And Configuring Azure AD Connect Health For Active Directory Domain Services

Posted by Jorge on 2016-07-20


This blog post show you how to configure Azure AD Connect Health for AD/ADDS

First download the Azure AD Connect Health Agent for AD/ADDS from here

After the download move/copy the executable to every DC you want to install the Azure AD Connect Health for AD/ADDS on

Double-click on the executable

Click [Install]

image

Figure 1: Azure AD Connect Health – Install Screen

After the installation ends, you need to determine how your DCs have access to the internet.

If the DC has a direct connection, you need to only open up firewall ports first before continuing.

If the DC must have access through a proxy, you must configure the proxy settings first before continuing

There are several options to source the proxy settings from

[1] If you want to source the settings from Internet Explorer use: Set-AzureAdConnectHealthProxySettings -ImportFromInternetSettings

[2] If you want to source the settings from WinHTTP use: Set-AzureAdConnectHealthProxySettings -ImportFromWinHttp

[3] If you want to source the settings manually use: Set-AzureAdConnectHealthProxySettings -HttpsProxyAddress <PROXYSERVER>:<PORT>

Afterwards you can view the proxy settings in use by Azure AD Connect Health through: Get-AzureAdConnectHealthProxySettings

Additional info regarding the URLs accessed by Azure AD Connect Health see: https://azure.microsoft.com/en-us/documentation/articles/active-directory-aadconnect-health-agent-install/

If your DC supports cookies and JavaScript or IE Enhanced Security Configuration has been disabled:

Click [Configure Now]

If your DC does not support cookies and JavaScript or IE Enhanced Security Configuration has been or is enabled:

Click [Close] and continue with figure 9

image

Figure 2: Azure AD Connect Health – Configure Screen

Specify either native Azure AD credentials or federated credentials. In either case, the credentials must have the Global Administrator role in Azure AD!

image

Figure 3: Azure AD Connect Health – Initial Credentials Screen

…because I used federated credentials, I’m redirected to ADFS

image

Figure 4: Azure AD Connect Health – Redirection To ADFS

…specify the password belonging to the specified user name

image

Figure 5: Azure AD Connect Health – Credentials Screen

…registration continues

image

Figure 6: Azure AD Connect Health – Registration Of The Agent

If you clicked [Configure Now] in figure 2 and your DC did not support cookies and JavaScript or IE Enhanced Security Configuration has been or is enabled, you will see the following message

Close the screen by clicking the red cross in the upper right corner

image

Figure 7: Azure AD Connect Health – Message About Cookies And/Or Javascript Not Being Supported

Closing the screen above throws the following errors

image

Figure 8: Azure AD Connect Health – Additional Errors

Open a new PowerShell command prompt window and type:

Import-Module AdHealthAdds

$azureADCreds = Get-Credential

Specify either native Azure AD credentials or federated credentials. In either case, the credentials must have the Global Administrator role in Azure AD!

image

Figure 9: Azure AD Connect Health – Starting Registration And Entering Native Azure Credentials

In the existing PowerShell command prompt window type:

Register-AzureADConnectHealthADDSAgent -Credential $azureADCreds

image

Figure 10: Azure AD Connect Health – Registration Of The Agent

Done! After doing on all the DCs, you can go to https://portal.azure.com/ and check the health of your AD/ADDS

PS: The installation of the Azure AD Connect Health Agent for ADFS is very similar!

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), Azure AD Connect Health, Azure AD Connect Health, Windows Azure Active Directory | Leave a Comment »

(2016-07-19) Azure AD Connect Health For Active Directory Domain Services Has Been Released

Posted by Jorge on 2016-07-19


Microsoft has released Azure AD Connect Health for AD/ADDS! COOL!

More information:

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), Azure AD Connect Health, Azure AD Connect Health, Windows Azure Active Directory | Leave a Comment »

(2016-06-16) A Change In Group Policy Processing After The Security Update MS16-072

Posted by Jorge on 2016-06-16


The Security Update MS16-072 changes the way how user GPOs are being processed. If you do not meet pre-requisites, make sure you do before applying the security update!

This applies to every Windows version starting from Windows Server 2008 and Windows Vista and higher!

More about this:

Symptoms

All user Group Policy, including those that have been security filtered on user accounts or security groups, or both, may fail to apply on domain joined computers.

Cause

This issue may occur if the Group Policy Object is missing the Read permissions for the Authenticated Users group or if you are using security filtering and are missing Read permissions for the domain computers group.

Resolution

To resolve this issue, use the Group Policy Management Console (GPMC.MSC) and follow one of the following steps:

Add the Authenticated Users group with Read Permissions on the Group Policy Object (GPO).
If you are using security filtering, add the Domain Computers group with read permission.

Script

If you want to script this have a look at:

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), Group Policy Objects | 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-01-19) Free AD Tool For The IT Pro (4)

Posted by Jorge on 2016-01-19


While browsing the internet I found the following AD related tools that might be worth checking out. Have fun!

REMARK: I do not own and do not support these tools. These tools are also not specifically recommended by me, this post is just a "FYI only!" It is your responsibility to test and check out these tools to see if these meet your requirements.

Z-Hire/Z-Term Active Directory, Exchange, Lync, Office 365 User Creation Tool

Z-Hire automates the IT account creation process for Exchange mailbox, Active Directory, Lync accounts, Office 365 cloud and SalesForce cloud deployments. With just a click of the button, your Exchange mailbox, and Active directory user and Lync accounts will be created simultaneousy. This tool can also create and set custom settings for Office 365 accounts using templates. Z-Hire serves as the platform for new hire accounts by allowing auto-creation of major IT user accounts with the option for custom scripts. Z-hire will decrease your new hire user account deployment time by 600%, without the need for complicated and expensive identity management solutions. This Active Directory User Creation Tool makes creating Active Directory users a breeze. Some of the features include:

  • Environment Auto detection/discovery (AD/Exchange/Lync/Office 365/SalesForce)
  • Copy existing Active Directory User to Z-Hire Template
  • Support for Active Directory user, Exchange Mailbox, Lync 2010, Lync 2013, Office 365 user and SalesForce user account
  • Template based deployment (allows consistency for all user accounts)
  • Office 365 account creation with major attributes
  • Office 365 license only mode (assign license only, when using DirSync)
  • Office 365 Hybrid mode ( for organizations running Office 365 in Hybrid mode)
  • Active Directory user account creation with major attributes
  • Active Directory group selection
  • Active Directory user duplicate SamAccountName verification
  • Lync 2010 account creation supporting all policies
  • SalesForce user creation support all major attributes
  • Faster performance (compared to previous version)
  • Bulk import from CSV / Text to provision Active Directory, Exchange, Lync and Office 365 users (version 5.3)
  • HRIS / WorkDay driven user provisioning (Automatically provision users from WorkDay and other HRIS Systems)
  • HRIS / WorkDay driven data sync (Automatically sync user data such as Title, Department from WorkDay and other HRIS systems)

Click HERE for more information

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), Exchange Server, IT Pro Tools, OCS/Lync Server, Office 365, Windows Azure Active Directory | Leave a Comment »

(2015-12-17) The “Get-ADPermission” CMDlet Reports A Corrupted ACE

Posted by Jorge on 2015-12-17


You are managing Exchange through PowerShell and this time you are checking the AD permissions on a user account with a mailbox. For that you use the following PwoerShell command line:

Get-Mailbox -identity "<identity value>" | Get-ADPermission | Sort user | FT –AutoSize

The “<identity value>” specifies the mailbox that you want to view. You can use any value that uniquely identifies the mailbox. Values of example are:

  • Name
  • Display name
  • Alias
  • Distinguished name (DN)
  • Canonical DN
  • <domain name>\<account name>
  • Email address
  • GUID
  • LegacyExchangeDN
  • SamAccountName
  • User ID or user principal name (UPN)

The command executes and you are confronted with the following, or similar, error:

WARNING: The object IAMTEC.NET/Org-Users/EMPLOYEES/Users/ICT/Jorge.deAlmeidaPint has been corrupted, and it’s in an inconsistent state. The following validation errors happened:

WARNING: The access control entry defines the ObjectType ’18d30bdd-e673-6047-b13c-cffee0abb929′ that can’t be resolved..

image

Figure 1: The PowerShell CMDlet “Get-ADPermission” Throws Error About Corrupted ACE

After seeing this error you may think about incorrect ordering of ACEs, as explained in the blog post (2014-08-04) Incorrectly Ordered Permissions After Removing ACE With LDP, or might even think your AD schema is in a bad shape.

This time, that is not the case.

Let’s investigate this step by step and see where the so called corruption came from!

When an ACE is defined, the specified security principal (user, group or computer) is assigned the specified permissions for an objectClass or for an attributeClass within a objectClass. In either case the specified objectClass or attributeClass is always translated under the hood to its corresponding schemaIDGUID. Every objectClass and attributeClass in the AD schema must have and has a unique schemaIDGUID value. When extending the AD schema it is not mandatory to specify a schemaIDGUID value in base64 format. If you do not specify a schemaIDGUID value, AD will generate a unique value for you. However, that is NOT a best practice. When extending the AD schema you should always specify a unique schemaIDGUID value. If you intend to deploy your schema extension to multiple AD forests, then the schemaIDGUID for the extended objectClass or attributeClass across all AD forest is preferably the same schemaIDGUID value.

The error mentions “ObjectType ’18d30bdd-e673-6047-b13c-cffee0abb929’”. The specified GUID value is NOT the objectGUID, it is the schemaIDGUID value of the objectClass or attributeClass that is causing the error. So, let’s search using the specified schemaIDGUID value.

For I’m going to use PowerShell through the following command to find the objectClass or attributeClass:

$rawGuid = ([guid]’18d30bdd-e673-6047-b13c-cffee0abb929′).toByteArray();

Get-ADObject -Filter {schemaIDGUID -eq $rawGuid} -SearchBase (Get-ADRootDSE).schemaNamingContext -Properties * | FL

The query result is:

image

Figure 2: Result Of The PowerShell Query In Searching For An objectClass Or attributeClass Through The schemaIDGUID

The name of the attributeClass already tells you how special it is, but that’s because I named it like that. In reality you will not see that. The real answer is hidden in the searchFlags property. If you look above you will 904, and that does not tell you much, unless you decode it yourself, or use ADFIND from joeware.net. (@joe: stop laughing!)

Now let’s use ADFIND through the following command to find the objectClass or attributeClass:

adfind -schema -f "schemaIDGUID={{GUID:18d30bdd-e673-6047-b13c-cffee0abb929}}" -binenc –flagdc

The query result is

image

Figure 3: Result Of The ADFIND Query In Searching For An objectClass Or attributeClass Through The schemaIDGUID

Of course ADFIND will return the same attributeClass, but at the same time it decoded searchFlags and systemFlags. When looking at the searchFlags property you will see the attribute has been configured with the confidentiality bit. Now, what’s special about that? You may remember it or not, but to be able to read the data in a confidential attribute, you must be either a member of “Domain Admins” or a member of “Account Operators” or a member of any other group that has Full Control on the corresponding object, or you must have at least the “Control Access” extended right.

Now the ONLY tools, I know of, that can both read (interpreter) and set the “Control Access” extended right, are DSACLS, LDP and ADFIND/ADMOD. Any other tools, may not process/interpreter it correctly. Those tools then either do not display the “Control Access” extended right or throw some weird error, like the PowerShell CMDet “Get-ADPermission”.

Now you might think “Yeah right. Your attribute is a custom AD schema extension and its has been implemented/designed in a bad way!”. Again, that’s not the case. This AD schema extension or any other AD schema I have ever designed has followed best practices! By default, the AD schema also contains attributeClasses that have been configured with the confidentiality bit. To find those attributeClasses, read the (2014-12-19) Finding Attributes Marked As Confidential. Now choose an attribute you like, and configure it with the “Control Access” extended right (do this in a TEST environment).

To configure the “Control Access” extended right for the confidential attribute on an OU that targets user objects, execute the following command:

DSACLS "<DN of OU>" /G "<Security Pincipal>:CA;<Confidential Attribute>;user" /I:S

Now, execute the Get-ADPermission CMDlet as specified at the beginning of this blog post. You should see a similar error about a corrupted ACE.

Therefore the conclusion is, that this is caused by the GET-ADPermission CMDlet (bug!) that misinterpreters the “Control Access” extended right. I know this occurs in Exchange Server 2010 and Exchange Server 2013, and most likely also in Exchange Server 2016. The Exchange Product Team, most likely, will never fix this as it is very low priority and there is no real issue here. If you really want to change the behavior of the GET-ADPermission CMDlet, then you need to submit a Design Change Request (DCR) will and impact analysis, including any monetary harm or loss you have due to this behavior. Good luck in trying to do that! You are better off ignoring this and paying real attention to some other stuff that’s more important! 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 Active Directory Domain Services (ADDS), Confidential, Exchange Server, PowerShell | 5 Comments »

(2015-12-06) The Cool Stuff About Active Directory In Windows Server 2016 TP4

Posted by Jorge on 2015-12-06


Microsoft recently released Windows Server 2016 Technical Preview 4, and with that also the technical details of what currently is new in Active Directory.

More Information:

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), What's New | Leave a Comment »

(2015-11-08) Kerberos Constrained Delegation (KCD) Visualized The Easy Way

Posted by Jorge on 2015-11-08


Kerberos Constrained Delegation (KCD) in general can be very difficult to understand, especially if you take all the possible scenarios into account. This post is not about explaining KCD old-style (pre-Windows Server 2012) and/or KCD new-style (Windows Server 2012 and higher). It is rather about visualizing the possible scenarios I could think of. Hopefully your scenario is included, so that you can easily see what is possible or not.

As an example of services I use a web based front-end and a SQL based back-end.

In all the scenarios specified below the following applies:

image

Figure 1: Types Of User Accounts Used In KCD Setups

KCD Old-Style, supported by both pre-Windows Server 2012 Servers/DCs and Windows Server 2012 Servers/DCs and Higher 

To support KCD Old-Style, the following must be true:

  • Front-End Server is running Windows Server 2008 R2 or lower
    OR
  • Back-End Server is running Windows Server 2008 R2 or lower
    OR
  • At least 1 RWDC running Windows Server 2012 is not available in the same AD domain as the front-end server
    OR
  • At least 1 RWDC running Windows Server 2012 is not available in the same AD domain as the back-end server
    OR
  • KCD is be configured through delegated kerberos constrained delegation (targeted resource is not configured to have the impersonating service account listed in the attribute “msDS-AllowedToActOnBehalfOfOtherIdentity” and delegated account is rather configured to have the target resource listed in the attribute “msDS-AllowedToDelegatedTo”)
    OR
  • Any of the above combined in some way

[Scenario 1]

image

Figure 2: KCD Old Style – Single Domain Forest

[Scenario 2]

image

Figure 3: KCD Old Style – Multiple Single Domain Forests

[Scenario 3]

image

Figure 4: KCD Old Style – Multiple Domain Forest

KCD New-Style, supported by only Windows Server 2012 Servers/DCs and Higher

To support KCD New-Style, the following must be true:

  • Front-End Server must run Windows Server 2012 or higher
    AND
  • Back-End Server must run Windows Server 2012 or higher
  • AND
  • At least 1 RWDC running Windows Server 2012 must be available in the same AD domain as the front-end server. Other RWDCs/RODCs running either Windows Server 2008 or Windows Server 2008 R2 must have the hotfix MS-KBQ2665790 installed
    AND
  • At least 1 RWDC running Windows Server 2012 must be available in the same AD domain as the back-end server. Other RWDCs/RODCs running either Windows Server 2008 or Windows Server 2008 R2 must have the hotfix MS-KBQ2665790 installed
    AND
  • KCD must be configured through resource-based kerberos constrained delegation (targeted resource must have the impersonating service account listed in the attribute “msDS-AllowedToActOnBehalfOfOtherIdentity”)

[Scenario 1]

image

Figure 5: KCD New Style – Single Domain Forest

[Scenario 2]

image

Figure 6: KCD New Style – Multiple Domain Forest

[Scenario 3]

image

Figure 7: KCD New Style – Multiple Single Domain Forests

[Scenario 4]

image

Figure 8: KCD New Style – Multiple Single Domain Forests

[Scenario 5]

image

Figure 9: KCD New Style – Multiple Single Domain Forests

[Scenario 6]

image

Figure 10: KCD New Style – Multiple Single Domain Forests

[Scenario 7]

image

Figure 11: KCD New Style – Multiple Single Domain Forests (Similar To KCD Old Style In Figure 3)

[Scenario 8]

image

Figure 12: KCD New Style – Multiple Single Domain Forests (Similar To KCD Old Style In Figure 4)

To read more and learn about all the dirty details, have a look at the following blog posts/articles:

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), Kerberos AuthN, Kerberos Constrained Delegation | Leave a Comment »

(2015-10-18) Notifying Users By E-mail Their Password Is Going To Expire (Update 4)

Posted by Jorge on 2015-10-18


UPDATE 2016-05-09: see https://jorgequestforknowledge.wordpress.com/2016/05/09/notifying-users-by-e-mail-their-password-is-going-to-expire-update-5/

Almost 6 years ago 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

Figure 1a: XML Configuration File

image_thumb29_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

Figure 2a: Output To Screen

image_thumb35_thumb_thumb

Figure 2b: Output To Screen

image_thumb38_thumb_thumb

Figure 2c: Output To Screen

image_thumb41_thumb_thumb

Figure 2d: Output To Screen

image_thumb44_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

Figure 3a: E-Mail Notification In English

image_thumb51_thumb_thumb

Figure 3b: E-Mail Notification In English

E-mail Message For Dutch Language

image_thumb55_thumb_thumb

Figure 4a: E-Mail Notification In Dutch

image_thumb58_thumb_thumb

Figure 4b: E-Mail Notification In Dutch

And Finally….The PowerShell Script Itself

Version of version: v0.18

Date of script: 2015-10-13

### 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) ### <# .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.18" $scriptDate = "2015-10-13" ################################################################################################## #################################### 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 "" 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") { ### 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) { $pwdPolicyInDomain = @() $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 | 5 Comments »

(2015-10-07) Realistic Random Data Set To Import Into Some Identity Store

Posted by Jorge on 2015-10-07


Have you ever required to have a large and realistic random data set to test your application or system in some way? Well, look no further!

With testing, “performance/volume testing” and/or “logic testing” (either declarative or coded, against small and large data sets) is meant. Testing with correctly defined (custom) data is required to make sure the application/system behaves as you require it to behave. By using fake data you are sure you do not get into trouble due to privacy or security related issues. You also do not have to beg for and jump through all kinds of hoops to get the data. Depending, on your organization, you may also need to have a data set that includes special characters (e.g. apostrophes) and/or very special characters (e.g. unicode characters from other languages)

Most likely, there are more websites out there, but the following 2 websites can help you out in different scenarios:

  1. https://www.mockaroo.com/
  2. http://www.fakenamegenerator.com/order.php

[1] Mockaroo – Realistics Data Generator

This website allows you to use your own defined schema. You can do that by selecting/defining the field names and field types or by importing the field headers of some CSV file you have. After importing the CSV headers, you still need to define the field types. When done, you can preview the data or download it right away. The data can be downloaded in different formats, such as, but not limited to, CSV format. The only downsides are the limited number of objects (max. 1000) and that it only supports western characters for names. If you need more data, you need to pay a fee per year.

image

Figure 1: The Interface Of The Mockaroo Website To Define The Required Schema

[2] Fake Name Generator

However, if you do not have a strict schema, you want up to 50.000 objects and you also require non-western characters for names (e.g. japanese, chinese, arabic, etc.), then you might be interesting in using this website. You can generate data for a single object, or you can bulk generate (order for free!) a very large amount of data up to 50.000 objects. If you need more objects, you just request it multiple times.

First you need to select the format and compression type. Secondly you need to select the name set(s), countries, gender and age of that objects.

image

Figure 2a: The Interface Of The Fake Name Generator Website To Define The Configuration For The Data Set

And last but not least, you need to select the required fields you want to include in the data set, define the required number of objects and the e-mail address where the bulk order is e-mailed to.

image

Figure 2b: The Interface Of The Fake Name Generator Website To Define The Configuration For The Data Set

Every request file is made available after a few minutes and when done you will receive an e-mail with a time-limited link.

After receiving the data set you can import it, by first writing your own PowerShell script, into ADDS, ADLDS, Azure AD, FIM Portal/Sync, SQL database or anything similar

Have fun!

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), Active Directory Lightweight Directory Services (ADLDS), Data Set, Data Set, Data Set, Data Set, Forefront Identity Manager (FIM) Portal, Windows Azure Active Directory | Leave a Comment »

 
%d bloggers like this: