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

(2015-05-23) Generating Self-Signed Certificates (For Testing Purposes)

Posted by Jorge on 2015-05-23


With MAKECERT you could create/generate self-signed certificates that you could use for testing purposes or in your test environmnt. MAKECERT has been deprecated for reasons described here. In Windows 8.1, Windows PowerShell 4.0, Windows Server 2012 R2 you can use the New-SelfSignedCertificate CMDlet. However, that CMDlet has its own limitations. By default it uses the following hardcoded limitations, which unfortunately cannot be changed:

  • Subject: Empty
  • Key: RSA 2048
  • EKUs: Client Authentication and Server Authentication
  • Key Usage: Digital Signature, Key Encipherment (a0)
  • Validity Period: One year

One of the best PKI guys I know about, is Vadims Podāns, and he wrote is own New-SelfSignedCertificateEx CMDlet a few years ago which quite some flexibility. For example, you can specify the "Not Before" and "Not After" date, and therefore determine the lifetime of the self-signed certificate. It is included in a PowerShell script, which can be downloaded from here.

Unfortunately I could not get it to work. I got the PowerShell script working by doing the following:

  • Downloading the PowerShell script
  • Replace the line "function New-SelfSignedCertificateEx {" with "#function New-SelfSignedCertificateEx {"
  • Replace the last line with a "}" with "#}" (line number 392)
  • Remove the certificate block
  • Change the execution policy in PowerShell

For my testing purposes I used the following PowerShell command (make sure to replace some parameter values with the values you need)

.\New-SelfsignedCertificateEx.ps1 -Subject "CN=TESTTOKEN.IAMTEC.NL" -EnhancedKeyUsage "Server Authentication","Client authentication" -KeyUsage "KeyEncipherment","DigitalSignature" -SubjectAlternativeName "DNS Name=TESTTOKEN.IAMTEC.NL" -StoreLocation "LocalMachine" -ProviderName "Microsoft Enhanced Cryptographic Provider v1.0" -AlgorithmName RSA -KeyLength 2048 -SignatureAlgorithm SHA256 -NotBefore $([datetime]::now.AddDays(-345)) -NotAfter $([datetime]::now.AddDays(15)) -FriendlyName "My Test Self-Signed Certificate" -exportable

image

Figure 1: Generating A Self-Signed Certificate With The Custom PoSH Script

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 Certificates, PowerShell, Tooling/Scripting | Leave a Comment »

(2015-05-19) Configuring Windows Integrated AuthN For Chrome Against ADFS v3.0 And Higher

Posted by Jorge on 2015-05-19


ADFS by default supports multiple authentication mechanisms, being certificate authentication, forms based authentication (FBA) and Windows Integrated Authentication (WIA). For non-domain joined clients or clients on the extranet, FBA is the best option. For domain-joined client on the intranet, WIA is the best option to use. In the last scenario WIA delivers the best SSO experience for the user. To support WIA, the backend, the client and the ADFS server(s) must be configured to support WIA.

[1] To support WIA on the backend the following must be true:

  • The ADFS service account must be configured with a service principal name "HOST/<Federation Service FQDN>" (e.g. HOST/FS.COMPANY.COM)

[2] To support WIA on the client/browser the following must be true:

  • The browser must support JavaScript and have it enabled–> For Chrome this is enabled by default. However, if you have disabled it follow the next steps to re-enable it:
    • Enable it globally"
      • Start Chrome
      • Click the "Chrome Menu" button (icon with 3 stacked horizontal lines)
      • Select "Settings"
      • In the "Settings" Window –> Scroll to the bottom of the page and click the link "Show Advanced Settings" –> "Privacy" Section –> Click the "Content Settings" button
      • "Javascript" Section –> Select "Allow all sites to run JavaScript"
      • Click "Done"
    • Enable it specifically for the Federation Service FQDN and any other FQDN requiring it
      • Start Chrome
      • Click the "Chrome Menu" button (icon with 3 stacked horizontal lines)
      • Select "Settings"
      • In the "Settings" Window –> Scroll to the bottom of the page and click the link "Show Advanced Settings" –> "Privacy" Section –> Click the "Content Settings" button
      • "Javascript" Section –> Click the "Manage Exceptions" button
      • Specify the Federation Service FQDN and click "Allow"
      • Click "Done" 2x
  • The browser must support and enable the use of cookies –> For Chrome this is enabled by default. However, if you have disabled the use of cookies, you can follow the next steps to re-enable it:
    • Enable it globally"
      • Start Chrome
      • Click the "Chrome Menu" button (icon with 3 stacked horizontal lines)
      • Select "Settings"
      • In the "Settings" Window –> Scroll to the bottom of the page and click the link "Show Advanced Settings" –> "Privacy" Section –> Click the "Content Settings" button
      • "Cookies" Section –> Select "Allow local data to be set (recommended)"
      • Click "Done"
    • Enable it specifically for the Federation Service FQDN and any other FQDN requiring it
      • Start Chrome
      • Click the "Chrome Menu" button (icon with 3 stacked horizontal lines)
      • Select "Settings"
      • In the "Settings" Window –> Scroll to the bottom of the page and click the link "Show Advanced Settings" –> "Privacy" Section –> Click the "Content Settings" button
      • "Cookies" Section –> Click the "Manage Exceptions" button
      • Specify the Federation Service FQDN and click "Allow"
      • Click "Done" 2
  • WIA is enabled in the browser –> For Chrome this is disabled by default. However, if you have it disabled follow the next steps to enable it:
    • Start Registry Editor
    • Navigate to the key "HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome"
    • If the key "AuthSchemes" does not exist, create it
    • In the key "AuthSchemes" create a string value (REG_SZ) named "AuthSchemes"
    • Assign the string value (REG_SZ) named "AuthSchemes", the values "ntlm,negotiate"
      REMARK: Specifies which HTTP Authentication schemes are supported by Google Chrome. Possible values are ‘basic’, ‘digest’, ‘ntlm’ and ‘negotiate’. Separate multiple values with commas. If this policy is left not set, all four schemes will be use
  • The federation service FQDN must be trusted –> For Firefox this is disabled by default for any FQDN on the internet and enabled for the intranet if detected. However, if you have it disabled follow the next steps to enable it for specific FQDNs (in this case the federation service FQDN must be trusted):
    • Start Registry Editor
    • Navigate to the key "HKEY_LOCAL_MACHINE\SOFTWARE\Google\Chrome"
    • If the key "AuthSchemes" does not exist, create it
    • In the key "AuthServerWhitelist" create a string value (REG_SZ) named "AuthServerWhitelist"
    • Assign the string value (REG_SZ) named "AuthServerWhitelist", the Federation Service FQDN as the value
      REMARK: Specifies which servers should be whitelisted for integrated authentication. Integrated authentication is only enabled when Google Chrome receives an authentication challenge from a proxy or from a server which is in this permitted list. Separate multiple server names with commas. Wildcards (*) are allowed. If you leave this policy not set Chrome will try to detect if a server is on the Intranet and only then will it respond to IWA requests. If a server is detected as Internet then IWA requests from it will be ignored by Chrome

[3] To support WIA against the ADFS STS server(s) the following must be true:

  • WIA must be enabled as an authentication method on the intranet
  • The user agent of the browser must be known to ADFS. By default ADFS v3.0 and higher, the following user agent strings are supported (retrieved through "(Get-AdfsProperties).WIASupportedUserAgents")
    • MSAuthHost/1.0/In-Domain <– ADFS v4.0 and higher
    • MSIE 6.0 <– ADFS v3.0 and higher
    • MSIE 7.0 <– ADFS v3.0 and higher
    • MSIE 8.0 <– ADFS v3.0 and higher
    • MSIE 9.0 <– ADFS v3.0 and higher
    • MSIE 10.0 <– ADFS v3.0 and higher
    • Trident/7.0 <– ADFS v3.0 and higher
    • MSIPC <– ADFS v3.0 and higher
    • Windows Rights Management Client <– ADFS v3.0 and higher
    • MS_WorkFoldersClient <– ADFS v4.0 and higher
  • You may need to disable extended protection in ADFS if you still keep being prompted for credentials while configured all of the above. I was NOT able to use Chrome v42.0.2311.135 and WIA without disabling extended protection
    REMARK: "ExtendedProtectionTokenCheck" –> Specifies the level of extended protection for authentication supported by the federation server. Extended Protection for Authentication helps protect against man-in-the-middle (MITM) attacks, in which an attacker intercepts a client’s credentials and forwards them to a server. Protection against such attacks is made possible through a Channel Binding Token (CBT) which can be either required, allowed or not required by the server when establishing communications with clients.

    Possible values for this setting are: as follows "Require" (server is full hardened, extended protection is enforced), "Allow" (server is partially hardened, extended protection is enforced where systems involved have been patched to support it) and "None" (Server is vulnerable, extended protection is not enforced). The default setting is "Allow". If lowering this protection is not acceptable, then use Forms-Based Authentication!

    • To disable ExtendedProtectionCheck on the ADFS server execute on the (primary) ADFS server: Set-AdfsProperties -ExtendedProtectionTokenCheck None
    • On every ADFS farm member, restart the ADFS service: Restart-Service ADFSSRV

So how do you know the state of your browser? Check it out yourself and just navigate to the website What’s My User Agent?

In my case the browser I was using, Chrome v42.0.2311.135, provided the shown user agent string and the yellow marked part matched what already was configured in ADFS

SNAGHTMLad46631

Figure 1: The User Agent String And Status Of Chrome 42.0.2311.135

The following is an analysis of the provided user agent string

image

Figure 2: Analysis Of The Provided User Agent String

So, for Chrome you need to meet the prerequisites as mentioned in [1], [2] and [3].

To configure a new supported user agent string in ADFS, while taking the existing user agent strings into account, use the following PowerShell commands on (primary) ADFS server:

Import-Module ADFS

Set-ADFSProperties -WIASupportedUserAgents $((Get-ADFSProperties).WIASupportedUserAgents + "<Required User Agent String>")

In this case the command is:

Import-Module ADFS

Set-ADFSProperties -WIASupportedUserAgents $((Get-ADFSProperties).WIASupportedUserAgents + "Mozilla/5.0")

That’s it! You should now be able to use Chrome and experience seamless SSO!

For other user agent strings also see:

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

Posted in Active Directory Federation Services (ADFS), Windows Integrated AuthN | Leave a Comment »

(2015-05-15) Configuring Windows Integrated AuthN For Firefox Against ADFS v3.0 And Higher

Posted by Jorge on 2015-05-15


ADFS by default supports multiple authentication mechanisms, being certificate authentication, forms based authentication (FBA) and Windows Integrated Authentication (WIA). For non-domain joined clients or clients on the extranet, FBA is the best option. For domain-joined client on the intranet, WIA is the best option to use. In the last scenario WIA delivers the best SSO experience for the user. To support WIA, the backend, the client and the ADFS server(s) must be configured to support WIA.

[1] To support WIA on the backend the following must be true:

  • The ADFS service account must be configured with a service principal name "HOST/<Federation Service FQDN>" (e.g. HOST/FS.COMPANY.COM)

[2] To support WIA on the client/browser the following must be true:

  • The browser must support JavaScript and have it enabled–> For Firefox this is enabled by default. However, if you have installed the privacy extension "NoScript", then you need to either:
    • Enable it globally within the privacy extension "NoScript"
      • Start Firefox
      • Click the "NoScript" button
      • Select "Allow Scripts Globally"
      • Click "OK"
    • Enable it specifically for the Federation Service FQDN and any other FQDN requiring it
      • Start Firefox
      • Click the "NoScript" button
      • Select "Options"
      • Click the "Whitelist" tab
      • Specify the Federation Service FQDN and click "Allow"
      • Click "OK"
  • The browser must support and enable the use of cookies –> For Firefox this is enabled by default. However, if you have disabled the use of cookies, you can follow the next steps to re-enable it:
    • Enable it globally
      • Start Firefox
      • Click the "Firefox Menu" button (icon with 3 stacked horizontal lines)
      • Click the "Options" button
      • Click the "Privacy" panel
      • Check "Accept cookies from sites"
      • Click "OK"
    • Enable it specifically for the Federation Service FQDN and any other FQDN requiring it
      • Start Firefox
      • Click the "Firefox Menu" button (icon with 3 stacked horizontal lines)
      • Click the "Options" button
      • Click the "Privacy" panel
      • Click the "Exceptions" button
      • Specify the Federation Service FQDN and click "Allow"
      • Click "Close"
      • Click "OK"
  • WIA is enabled in the browser for the federation service FQDN –> For Firefox this is disabled by default for any FQDN. However, if you have it disabled follow the next steps to enable it for specific FQDNs (in this case the federation service FQDN must be trusted):
    • Start Firefox
    • As a URL type "about:config"
    • Click the "I’ll be careful, I promise!" button
    • As a Search type "network.negotiate-auth.trusted-uris"
    • Double-click on the line "network.negotiate-auth.trusted-uris"
    • Specify the Federation Service FQDN, or a comma separated list and click "OK"
    • Double-click on the line "network.automatic-ntlm-auth.trusted-uris"
    • Specify the Federation Service FQDN, or a comma separated list and click "OK"

[3] To support WIA against the ADFS STS server(s) the following must be true:

  • WIA must be enabled as an authentication method on the intranet
  • The user agent of the browser must be known to ADFS. By default ADFS v3.0 and higher, the following user agent strings are supported (retrieved through "(Get-AdfsProperties).WIASupportedUserAgents")
    • MSAuthHost/1.0/In-Domain <– ADFS v4.0 and higher
    • MSIE 6.0 <– ADFS v3.0 and higher
    • MSIE 7.0 <– ADFS v3.0 and higher
    • MSIE 8.0 <– ADFS v3.0 and higher
    • MSIE 9.0 <– ADFS v3.0 and higher
    • MSIE 10.0 <– ADFS v3.0 and higher
    • Trident/7.0 <– ADFS v3.0 and higher
    • MSIPC <– ADFS v3.0 and higher
    • Windows Rights Management Client <– ADFS v3.0 and higher
    • MS_WorkFoldersClient <– ADFS v4.0 and higher
  • You may need to disable extended protection in ADFS if you still keep being prompted for credentials while configured all of the above. I was able to use Firefox v37.0 and WIA without disabling extended protection
    REMARK: "ExtendedProtectionTokenCheck" –> Specifies the level of extended protection for authentication supported by the federation server. Extended Protection for Authentication helps protect against man-in-the-middle (MITM) attacks, in which an attacker intercepts a client’s credentials and forwards them to a server. Protection against such attacks is made possible through a Channel Binding Token (CBT) which can be either required, allowed or not required by the server when establishing communications with clients.

    Possible values for this setting are: as follows "Require" (server is full hardened, extended protection is enforced), "Allow" (server is partially hardened, extended protection is enforced where systems involved have been patched to support it) and "None" (Server is vulnerable, extended protection is not enforced). The default setting is "Allow". If lowering this protection is not acceptable, then use Forms-Based Authentication!

    • To disable ExtendedProtectionCheck on the ADFS server execute on the (primary) ADFS server: Set-AdfsProperties -ExtendedProtectionTokenCheck None
    • On every ADFS farm member, restart the ADFS service: Restart-Service ADFSSRV

So how do you know the state of your browser? Check it out yourself and just navigate to the website What’s My User Agent?

In my case the browser I was using, Firefox v37.0, provide the shown user agent string and the yellow marked part did not match what already was configured in ADFS

SNAGHTMLa8467bc

Figure 1: The User Agent String And Status Of Firefox 37.0

The following is an analysis of the provided user agent string

image

Figure 2: Analysis Of The Provided User Agent String

So, for Firefox you need to meet the prerequisites as mentioned in [1], [2] and [3].

To configure a new supported user agent string in ADFS, while taking the existing user agent strings into account, use the following PowerShell commands on (primary) ADFS server:

Import-Module ADFS

Set-ADFSProperties -WIASupportedUserAgents $((Get-ADFSProperties).WIASupportedUserAgents + "<Required User Agent String>")

In this case the command is:

Import-Module ADFS

Set-ADFSProperties -WIASupportedUserAgents $((Get-ADFSProperties).WIASupportedUserAgents + "Mozilla/5.0")

That’s it! You should now be able to use Firefox and experience seamless SSO!

For other user agent strings also see:

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

Posted in Active Directory Federation Services (ADFS), Windows Integrated AuthN | Leave a Comment »

(2015-05-11) Configuring Windows Integrated AuthN For Internet Explorer Against ADFS v3.0 And Higher

Posted by Jorge on 2015-05-11


ADFS by default supports multiple authentication mechanisms, being certificate authentication, forms based authentication (FBA) and Windows Integrated Authentication (WIA). For non-domain joined clients or clients on the extranet, FBA is the best option. For domain-joined client on the intranet, WIA is the best option to use. In the last scenario WIA delivers the best SSO experience for the user. To support WIA, the backend, the client and the ADFS server(s) must be configured to support WIA.

[1] To support WIA on the backend the following must be true:

  • The ADFS service account must be configured with a service principal name "HOST/<Federation Service FQDN>" (e.g. HOST/FS.COMPANY.COM)

[2] To support WIA on the client/browser the following must be true:

  • The browser must support JavaScript and have it enabled–> For IE this is enabled by default. However, if you have disabled it follow the next steps to re-enable it:
    • Start Internet Explorer
    • "Tools" menu/button
    • Click "Internet Options"
    • Click the "Security" tab
    • Click the "Local Intranet" or "Trusted Sites" zone (assuming Federation Service FQDN is listed in either one!)
    • Click "Custom Level"
    • In the "Security Settings" Window –> "Scripting" Section –> Select "Enable" for Active Scripting
    • Click "OK" 2x
  • The browser must support and enable the use of cookies for sites in the Local Intranet or Trusted Sites zone –> For IE this is enabled by default as all cookies are automatically accepted or replayed from Web sites in both the Local Intranet and the Trusted zones
  • WIA is enabled in the browser –> For IE this is enabled by default. However, if you have disabled it follow the next steps to re-enable it:
    • Start Internet Explorer
    • "Tools" menu/button
    • Click "Internet Options"
    • Click the "Advanced" tab
    • In the "Settings" Window –> "Security" Section –> Check "Enable Integrated Windows Authentication"
    • Click "OK"
  • The federation service FQDN must be trusted –> For IE this federation service FQDN is configured in either the "Local Intranet" zone or the "Trusted Sites" zone. To add the Federation Service FQDN manually to either zone follow the next steps:
    • Start Internet Explorer
    • "Tools" menu/button
    • Click "Internet Options"
    • Click the "Security" tab
    • Click the "Local Intranet" or "Trusted Sites" zone
      • "Local Intranet" –> Click "Sites" –> Click "Advanced" –> Add Federation Service FQDN to the list
      • "Trusted Sites" –> Click "Sites" –> Add Federation Service FQDN to the list
    • Click "OK"

[3] To support WIA against the ADFS STS server(s) the following must be true:

  • WIA must be enabled as an authentication method on the intranet
  • The user agent of the browser must be known to ADFS. By default ADFS v3.0 and higher, the following user agent strings are supported (retrieved through "(Get-AdfsProperties).WIASupportedUserAgents")
    • MSAuthHost/1.0/In-Domain <– ADFS v4.0 and higher
    • MSIE 6.0 <– ADFS v3.0 and higher
    • MSIE 7.0 <– ADFS v3.0 and higher
    • MSIE 8.0 <– ADFS v3.0 and higher
    • MSIE 9.0 <– ADFS v3.0 and higher
    • MSIE 10.0 <– ADFS v3.0 and higher
    • Trident/7.0 <– ADFS v3.0 and higher
    • MSIPC <– ADFS v3.0 and higher
    • Windows Rights Management Client <– ADFS v3.0 and higher
    • MS_WorkFoldersClient <– ADFS v4.0 and higher

So how do you know the state of your browser? Check it out yourself and just navigate to the website What’s My User Agent?

In my case the browser I was using, Internet Explorer v9.0, provided the shown user agent string and the yellow marked part matched what already was configured in ADFS

SNAGHTMLa6c2a2b

Figure 1: The User Agent String And Status Of Internet Explorer 9.0

The following is an analysis of the provided user agent string

image

Figure 2: Analysis Of The Provided User Agent String

So, for Internet Explorer you only need to meet the prerequisites as mentioned in [1] and [2]. For future versions of Internet Explorer you may need to also do [3] and update the user agent strings in ADFS that must support seamless SSO through WIA

To configure a new supported user agent string in ADFS, while taking the existing user agent strings into account, use the following PowerShell commands on (primary) ADFS server:

Import-Module ADFS

Set-ADFSProperties -WIASupportedUserAgents $((Get-ADFSProperties).WIASupportedUserAgents + "<Required User Agent String>")

That’s it! You should now be able to use Internet Explorer and experience seamless SSO!

For other user agent strings also see:

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

Posted in Active Directory Federation Services (ADFS), Windows Integrated AuthN | Leave a Comment »

(2015-05-07) FIM 2010 R2 Officially Supports W2K12R2 AD

Posted by Jorge on 2015-05-07


FIM 2010 R2, more specifically FIM Sync and PCNS, now officially support targeting a W2K12R2 AD. For that you need to at least install the hotfix (or later) as mention in (2015-05-03) A Hotfix Rollup Package (Build 4.1.3634.0) Is Available for Forefront Identity Manager 2010 R2 SP1

Note: In all supported cases, the FIM Synchronization Service must be installed only on a Windows Server 2008, Windows Server 2008 R2, or Windows Server 2012 member server. It must not be installed on a Windows Server 2012 R2 member server unless the PCNS component is installed on a Windows Server 2012 R2 domain controller.

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), Forefront Identity Manager (FIM) PCNS, Forefront Identity Manager (FIM) Sync | Leave a Comment »

(2015-05-06) Make Sure To Patch Your ADFS Infrastructure, Again!

Posted by Jorge on 2015-05-06


A few days ago Microsoft disclosed a serious vulnerability (MS15-040)  in ADFS v3.0 (ADFS in Windows Server 2012 R2). The vulnerability could allow information disclosure if a user leaves their browser open after logging off from an application and an attacker reopens the application in the browser immediately after the user has logged off. This security update is rated Important for AD FS 3.0 when installed on x64-based editions of Windows Server 2012 R2. The security update addresses the vulnerability by ensuring that the logoff process properly logs off the user.

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

Posted in Active Directory Federation Services (ADFS), Updates | Leave a Comment »

(2015-05-05) Make Sure To Patch Your ADFS Infrastructure, If You Have Not Done It Already

Posted by Jorge on 2015-05-05


Last month Microsoft disclosed a serious vulnerability (MS15-034) that exists in the HTTP protocol stack (HTTP.sys) that allows for remote code execution. This is caused when HTTP.sys improperly parses specially crafted HTTP requests. An attacker who successfully exploited this vulnerability could execute arbitrary code in the context of the System account. Microsoft also released a security update to patch Windows systems.

Now you may thing that you only need to patch Windows systems with IIS installed. That is not accurate. You also need to patch any system, even is IIS is not installed, that is built on top of HTTP.SYS. An example is ADFS v3.0 and higher.

This means that any system protected through ADFS is vulnerable if the ADFS infrastructure is compromised! If ADFS is compromised by someone, then that person is able to generate any security token with any claims in it, and gain access to claims-aware applications.

Therefore make sure to patch any Windows system with IIS or that is built on top of just HTTP.SYS!

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

Posted in Active Directory Federation Services (ADFS), Updates | Leave a Comment »

(2015-05-04) Office 2016 Public Preview Now Available

Posted by Jorge on 2015-05-04


This Monday (May 4th) during the Microsoft Ignite Conference Keynote Address, the Office 2016 Public Preview for Consumers and Enterprise customers has been announced

Read more here and here.

Download instructions here.

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

Posted in Office | Leave a Comment »

(2015-05-03) A Hotfix Rollup Package (Build 4.1.3634.0) Is Available for Forefront Identity Manager 2010 R2 SP1

Posted by Jorge on 2015-05-03


Microsoft released a new hotfix for FIM 2010 R2 SP1 with build 4.1.3634.0. What it fixes can be found in this blog post. For additional or detailed info see MS-KBQ3048056

Download link

Issues that are fixed or features that are added in this update

This update also fixes the following issues or adds the following features that were not previously documented in the Microsoft Knowledge Base.

Password Change Notification Service

Issue 1

Windows 2012 R2 Domain Controller Support

Supportability for Password Change Notification Service (PCNS) and Active Directory Management Agent in a Windows Server 2012 R2 domain and forest includes the following:

  • Password Change Notification Service is working correctly on Windows Server 2012 R2-based domain controllers.
  • Active Directory Management Agent for Windows 2012 R2 domain and forest correctly handles password change events.

Note In all supported cases, the FIM Synchronization Service must be installed only on a Windows Server 2008, Windows Server 2008 R2, or Windows Server 2012 member server. It must not be installed on a Windows Server 2012 R2 member server unless the PCNS component is installed on a Windows Server 2012 R2 domain controller.

Synchronization Service

Issue 1

When PCNS calls into the FIMSynchronizationService, the FIMSynchronizationService makes an LsaLookupNames2 API call. This call fails because of the format of the domain.

For example, the format of the domain fails and generates the following error:

BAIL: MMS(3120): d:\bt\35150\private\source\miis\password\listener\pcnslistener.cpp(316): 0x80070534 (No mapping between account names and security IDs was done.): Win32 API failure: 1332
BAIL: MMS(3120): d:\bt\35150\private\source\miis\password\listener\pcnslistener.cpp(570): 0x80070534 (No mapping between account names and security IDs was done.)

FIM Service and Identity Management Portal

Issue 1

Custom search scope

Incorrect results are shown when multiple question mark (?) characters are found.

Issue 2

Comma-separated values (CSV) format is not supported for file upload for UocFileDownload in an FIM resource control display configuration (RCDC). After you apply this hotfix, CSV format is added to the allow list of supported formats for this control.

Certificate Management

Issue 1

When a customer search returns too many objects, the following error is generated:

‘ADsDSOObject’ failed without an error message available, result code: -2147016669(0x80072023)

The method that is used, SearchUsers, did not use the DirectorySearcher object. Search is now updated to use the DirectorySearcher object.

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

 

Posted in Forefront Identity Manager (FIM) bHold, Forefront Identity Manager (FIM) Certificate Management, Forefront Identity Manager (FIM) PCNS, Forefront Identity Manager (FIM) Portal, Forefront Identity Manager (FIM) Sync, Updates, Updates, Updates, Updates | 1 Comment »

(2015-04-29) Notifying Users By E-mail Their Password Is Going To Expire (Update 2)

Posted by Jorge on 2015-04-29


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

Figure 1a: XML Configuration File

image_thumb29

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_v016.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_v016.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_v016.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_v016.ps1 -xmlconfigfilepath D:\TEMP\AD-Pwd-Exp-Notify.xml -force

Example Output Of The Script (On Screen)

image_thumb32

Figure 2a: Output To Screen

image_thumb35

Figure 2b: Output To Screen

image_thumb38

Figure 2c: Output To Screen

image_thumb41

Figure 2d: Output To Screen

image_thumb44

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

Figure 3a: E-Mail Notification In English

image_thumb51

Figure 3b: E-Mail Notification In English

E-mail Message For Dutch Language

image_thumb55

Figure 4a: E-Mail Notification In Dutch

image_thumb58

Figure 4b: E-Mail Notification In Dutch

And Finally….The PowerShell Script Itself

### 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) ### $scriptVersion = "v0.16" $scriptDate = "2015-04-29" <# .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 ) ################################################################################################## #################################### 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 " * --> AD PASSWORD EXPIRY NOTIFICATION <-- *" Logging " * ($scriptVersion) ($scriptDate) *" Logging " * Written By: Jorge de Almeida Pinto [MVP-DS] *" 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" $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" $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" $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 »

 
%d bloggers like this: