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-03-30) Querying The FIM Service Using PowerShell

Posted by Jorge on 2015-03-30


Have you ever needed to query the FIM service for objects based upon an Xpath definition and display the information on screen and if needed also export it to CSV?. Look no further, here’s a PowerShell script just doing that for you!

-

In the blog post (2012-11-11) Finding All Users Within FIM That Have (Not) Registered For SSPR I demonstrate how to query the FIM service using the FIM Portal for users that have registered for SSPR and for users that have not registered for SSPR. This is all done through search scopes. Once I got the question: "can you export that information?". It is by default not possible to export that information through the FIM Portal. However, nothing or nobody stops you from using PowerShell and achieve your goal!

-

Against using that blog post as an example, let’s do this through PowerShell. From that blog post I’m using the specified search scope filters to find both lists of users.

-

Finding Users That Have Registered For SSPR:

  • Search Scope Filter (XPath) In that Blog Post (Old Test Environment) –> "/Person[AuthNWFRegistered = /Set[ObjectID = ‘f6a599be-a292-40a7-8720-6bb445e47ad8′]/ComputerMember]"
  • Search Scope Filter (XPath) In My Current Test Environment –> "/Person[AuthNWFRegistered = /Set[ObjectID = ‘214ab7c5-afd8-4d57-bfe8-9c5b73ddc7e9′]/ComputedMember]"

.\Query-For-FIM-Service-Using-XPath.ps1 -xPath "/Person[AuthNWFRegistered = /Set[ObjectID = ‘214ab7c5-afd8-4d57-bfe8-9c5b73ddc7e9′]/ComputedMember]" -baseonly -exporttocsv -csvfilepath D:\TEMP\UsersThatHaveRegisteredForSSPR.CSV -attributelist IDtype,DisplayName,FirstName,LastName,EmployeeID,Email

image

Figure 1a: Output To Screen

-

image

Figure 1b: Output To CSV

-

Finding Users That Have NOT Registered For SSPR:

  • Search Scope Filter (XPath) In that Blog Post (Old Test Environment) –> "/Person[not(AuthNWFRegistered = /Set[ObjectID = ‘f6a599be-a292-40a7-8720-6bb445e47ad8′]/ComputerMember)]"
  • Search Scope Filter (XPath) In My Current Test Environment –> "/Person[not(AuthNWFRegistered = /Set[ObjectID = ‘214ab7c5-afd8-4d57-bfe8-9c5b73ddc7e9′]/ComputedMember)]"

.\Query-For-FIM-Service-Using-XPath.ps1 -xPath "/Person[not(AuthNWFRegistered = /Set[ObjectID = ‘214ab7c5-afd8-4d57-bfe8-9c5b73ddc7e9′]/ComputedMember)]" -baseonly -exporttocsv -csvfilepath D:\TEMP\UsersThatHaveNOTRegisteredForSSPR.CSV -attributelist IDtype,DisplayName,FirstName,LastName,EmployeeID,Email

image

Figure 2a: Output To Screen

-

image

Figure 2b: Output To CSV

-

And this is the PowerShell script….

-

# Abstract: This PoSH Script Exports Objects From The FIM Based Upon An XPath Definition, Converts It To PSObjects And Displays On Screen And Optionally Exports To CSV # Written by: Jorge de Almeida Pinto [MVP-DS] # Blog: https://jorgequestforknowledge.wordpress.com/ # # 2015-03-30: Initial version of the script # # Additional Information # * http://www.integrationtrench.com/2011/07/convert-fim-exportobject-to-powershell.html # * http://www.integrationtrench.com/2011/09/cant-use-xpath-contains-function-to.html # Example Syntaxes: # * <PoSH Script File> -xPath "/Person[AccountName='JohnDoe']" -baseonly # * <PoSH Script File> -xPath "/Person[AccountName='JohnDoe']" -baseonly -attributelist ObjectID,AccountName # * <PoSH Script File> -xPath "/Person[AccountName='JohnDoe']" -baseonly -exporttocsv -csvfilepath D:\TEMP\TEST.CSV # * <PoSH Script File> -xPath "/Person[AccountName='JohnDoe']" -baseonly -attributelist ObjectID,AccountName -exporttocsv -csvfilepath D:\TEMP\TEST.CSV Param ( # XPath Definition As Accepted By The FIM Service (e.g. "/Person[Account = 'JohnDoe']") [Parameter(Mandatory=$true)] [string]$xPath, # Comma-Separated List Of Attributes To Display/Export. When Nothing is Specified All Attributes Are Displayed/Exported [Parameter(Mandatory=$false)] [string[]]$attributelist, # The Full Path To The CSV File When Exporting To A CSV [Parameter(Mandatory=$false)] [string]$csvfilepath, # Export Only Based Objects (Recommended), Otherwise Also Export All Referred Objects [Parameter(Mandatory=$false)] [switch]$baseonly, # Also Export To CSV [Parameter(Mandatory=$false)] [switch]$exporttocsv ) Clear-Host Write-Host " ****************************************************" -ForeGroundColor Yellow Write-Host " ** Jorge de Almeida Pinto [MVP-DS] **" -ForeGroundColor Yellow Write-Host " ** BLOG: 'Jorge's Quest For Knowledge' **" -ForeGroundColor Yellow Write-Host " ** https://jorgequestforknowledge.wordpress.com/ **" -ForeGroundColor Yellow Write-Host " ** March 2015 **" -ForeGroundColor Yellow Write-Host " ****************************************************" -ForeGroundColor Yellow # MSFT PowerShell CMDlets For FIM 2010 R2 [array] $SnapInListToLoad = "FIMAutomation" foreach ($SnapIn In $SnapInListToLoad) { If(@(Get-PSSnapin | Where-Object {$_.Name -eq $SnapIn} ).count -eq 0) { If(@(Get-PSSnapin -Registered | Where-Object {$_.Name -eq $SnapIn} ).count -ne 0) { Add-PSSnapin $SnapIn Write-Host "" Write-Host "Snap-In '$SnapIn' has been loaded..." -ForeGroundColor Green Write-Host "" } Else { Write-Host "" Write-Host "Snap-In '$SnapIn' is not available to load..." -ForeGroundColor Red Write-Host "" } } Else { Write-Host "" Write-Host "Snap-In '$SnapIn' already loaded..." -ForeGroundColor Yellow Write-Host "" } } # Taken From http://www.integrationtrench.com/2011/07/convert-fim-exportobject-to-powershell.html Function Convert-FimExportToPSObject { Param ( [parameter(Mandatory=$true, ValueFromPipeline = $true)] [Microsoft.ResourceManagement.Automation.ObjectModel.ExportObject] $ExportObject ) Process { $psObject = New-Object PSObject $ExportObject.ResourceManagementObject.ResourceManagementAttributes | %{ If ($_.Value -ne $null) { $value = $_.Value } Elseif ($_.Values -ne $null) { $value = $_.Values } Else { $value = $null } $psObject | Add-Member -MemberType NoteProperty -Name $_.AttributeName -Value $value } Write-Output $psObject } } # If The BaseOnly Parameter Has Been Specified Then Only Export The Base Resources As Defined By The XPath Definition # Otherwise ALSO Export Referred Objects In Linked Attributes If ($baseonly) { $ObjectsInFIM = Export-FIMConfig -CustomConfig $xPath -OnlyBaseResources } Else { $ObjectsInFIM = Export-FIMConfig -CustomConfig $xPath } # If Additional Filtering Is Required Which Is Not Possible Through The Xpath Then Use: # http://www.integrationtrench.com/2011/09/cant-use-xpath-contains-function-to.html # Example: $ObjectsInFIM | Convert-FimExportToPSObject | ?{$_.Filter -like "*myAttribute*"} # Example: $ObjectsInFIM | Convert-FimExportToPSObject | ?{$_.XOML -like "*myValue*"} # !!! ==> ADJUST THE POWERSHELL MANUALLY TO BE ABLE TO USE THIS <== !!! # If The ExportCsv Parameter Has Been Specified Then ALSO Export To The CSV File Defined # Otherwise Just Show Information On Screen If ($exporttocsv) { $ObjectsInFIM | Convert-FimExportToPSObject | Select $attributelist | Export-CSV $csvfilepath -NoTypeInformation } $ObjectsInFIM | Convert-FimExportToPSObject | FT $attributelist -Autosize # Count The Number Of Objects $NumberOfObjectsInFIM = ($ObjectsInFIM | Measure-Object).Count Write-Host "Number Of Objects......: $NumberOfObjectsInFIM" Write-Host ""

-

Or get the PowerShell script from HERE

-

Cheers,

Jorge

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

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

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

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

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

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

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

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

Posted in Forefront Identity Manager (FIM) Portal, PowerShell, Tooling/Scripting, Xpath | Leave a Comment »

(2015-03-24) Notifying Users By E-mail Their Password Is Going To Expire (Update 1)

Posted by Jorge on 2015-03-24


UPDATE 2015-03-26: New version of the script has been published to solve some bugs

UPDATE 2015-03-27: New version of the script has been published to solve some bugs and the XML config file now also supports the specification of a date/time format

-

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

Figure 1a: XML Configuration File

-

image

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

-

Example Output Of The Script (On Screen)

image

Figure 2a: Output To Screen

-

image

Figure 2b: Output To Screen

-

image

Figure 2c: Output To Screen

-

image

Figure 2d: Output To Screen

-

image

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

Figure 3a: E-Mail Notification In English

-

image

Figure 3b: E-Mail Notification In English

-

E-mail Message For Dutch Language

image

Figure 4a: E-Mail Notification In Dutch

-

image

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) # # 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 # # 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 $dnsHostNameRWDC = ([System.DirectoryServices.ActiveDirectory.DomainController]::findone($contextADDomain)).Name 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 $scriptXMLConfigFilePath = Join-Path $currentScriptFolderPath "AD-Pwd-Exp-Notify.xml" } Else { $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 " * (v0.15) (2015-03-27) *" Logging " * Written By: Jorge de Almeida Pinto [MVP-DS] *" 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 $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") { Logging " --> SKIPPED DUE TO ERROR!" 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 BREAK } # 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" $pwdPolicyGPOInDomainObj.name = $($gpoName + " (" + $fqdnADdomain + ")") Logging "" Logging " --> Name............: $psoName" $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."Display Name" = "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 | 9 Comments »

(2015-03-20) A Hotfix Rollup Package (Build 4.1.3627.0) Is Available for Forefront Identity Manager 2010 R2

Posted by Jorge on 2015-03-20


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

Download link

-

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

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

-

BHOLD integration with the FIM Identity Management Portal and language packs

Issue 1

When a customer has installed the FIM Portal, BHOLD FIM Integration, and FIM language packs, a user who views a localized FIM Portal page sees the page revert to English after going to a BHOLD Self-Service page.

-

BHOLD Reporting

Issue 1

When a customer who uses BHOLD Reporting removes a filter from a report, successive reports continue to generate a report as if the filter was not removed.

-

BHOLD Model Generator

Issue 1

When you use the BHOLD Model Generator for role mining, and the Retain existing model check box is not selected, an exception may be reported when the BHOLD Model Generator loads files again.

-

Issue 2

When you use the BHOLD Model Generator for role mining, an exception may be reported when you create membership roles and proposed roles.

-

Issue 3

When you use the BHOLD Model Generator for role mining, if the user who’s running Model Generator does not exist in BHOLD, then attribute roles and ownership roles may not be created.

Issue 4

When the BHOLD Model Generator is run by a different user than who ran it previously or who installed BHOLD Core, and the Retain existing model check box is not selected, an exception may be reported.

-

FIM Service and Identity Management Portal

Issue 1

When multiple FIM Service computers are deployed, authentication workflow instances for SSPR requests that time out may remain in the Authenticating state indefinitely.

-

Issue 2

When you set or edit custom integer-valued attributes through the FIM Portal Extended attributes tab of an object, "Particular value is not supported" error message may be displayed for integer values that are greater than 2147483647.

Certificate Management

Issue 1

The FIM Certificate Management website incorrectly sends a request to the domain to look up the IdentityOneTimePasswordsRole+ FIM CM internal role.

-

Issue 2

When you select a user in the FIM CM Portal to manage a smart card, the following exception is triggered:

Object cannot be cast from DBNull to other types.
Technical Details
Type: System.InvalidCastException
Source: mscorlib
Stack Trace: at System.DBNull.System.IConvertible.ToDateTime(IFormatProvider provider)
at System.Convert.ToDateTime(Object value, IFormatProvider provider)
at Microsoft.Clm.DataAccess.Certificates.CalculateCertificateStatus(CertificateDataSet certData)
at Microsoft.Clm.BusinessLayer.Profiles.GetProfilesByStatus(Guid userUuid, ProfileStatus status)
at Microsoft.Clm.Web.UserDetail.LoadIntoInterface()
at Microsoft.Clm.Web.UserDetail.Page_Load(Object sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

-

Issue 3

FIM CM orphans smart cards in the FIMCertificateManagement database if initial enrollment for the smart card is tried under the incorrect profile template. This returns the following exception:

The card cannot be accessed because the wrong PIN was presented.

When you repeatedly try to enroll the smart card with the correct profile template, you receive the following error message:

Processing error: Invalid smart card. This card may only be reused for the user and profile template for which the card is assigned. Please retire the card first before enrolling for a different user or profile template.

Note After you apply this update rollup, when the wrong profile template fails with the "wrong PIN" exception, the following symptoms occur:

  • An incorrect administrator key is detected.
  • The smart card object in the database is moved to a retired state, without following the retire policy.
  • The user receives a message that states that the operation failed, probably because an incorrect profile template selection, and alternative profile templates are suggested.

-

Issue 4

When errors are encountered during the virtual smart card enrollment process, the virtual smart card on the computer is rolled back. However, the request in FIM Certificate Management remains in the "executing" state, making it possible for users to continue trying the existing request. However, the user cannot continue with the request because the virtual smart card being created on the computer no longer exists.

-

Issue 5

Expired or revoked logon certificates are no longer removed from smart card upon renewal in FIM CM.

This update adds functionality to remove certificates from smart cards on certificate expiration or revocation. This is a new option available in the Profile Template General Options settings page.

-

Synchronization Service

Issue 1

When the Active Directory global address list (GALSync) management agent is used against an Active Directory forest that hosts Exchange Server 2013, the GALSync solution does not generate the correct value for the msExchVersion attribute.

-

Issue 2

In a test environment, if there is no available connected data source for a particular kind of management agent, import audit trail files and export audit trail files are sometimes used to test the synchronization service solution functionality. When you use the FIM synchronization engine, this may trigger unexpected results.

The import and export operations seem to work, but on import, the run statistics show a delete and add for each object that’s exported to the drop file. Additionally, there are no objects left in the connectorspace.

This behavior occurs because the connectorspace object does not have the anchor value in the hologram as expected. Therefore, the connectorspace object is deleted and is intended to be replaced by the matching object that’s being imported. Additionally, the import add operation fails. The following workaround can be used in most scenarios.

-

Workaround

Important  Before you use this workaround, make sure that you have a current backup of the FIMSynchronizationService database.

The steps and SQL script that are documented in this workaround assume that you use a value from the metaverse object to populate the anchor value of new connectorspace objects during provisioning. If the management agent relies on the connected data source to create and provide anchor values for new objects, the provisioning code or synchronization rule may have to be temporarily changed to use a value from the metaverse to support this workaround.

This workaround is provided for a management agent with export and import run profiles configured as follows:

  • Export run profile that’s configured to create an audit trail file and stop the run
  • Import run profile configured to resume from audit trail file

To use this workaround, follow these steps:

  1. Run the export run profile.
  2. Run the script to update the FIMSynchronizationService database.
  3. Run the import run profile.

-

Sample script

The sample script that’s provided here is not intended for use in a production environment and is only meant to address the specific scenario that’s documented in this article in a development or test environment.

Before you run this SQL script, it must be changed to replace the management agent identifier (cs.ma_id). If a metaverse attribute other than UID must be used, the script must also be updated to replace all instances of "mv.uid" with the fieldname of the attribute to be used.

/*
 Fix anchor field in CS table for "broken" connectors after export to log file only
 Note:
 "mv.uid" should be fixed with valid field in MV table used as anchor
 "cs.ma_id = '2F2516F6-AD5B-4CFA-9F2B-AA4385D1879E'" should be fixed with valid id of target MA
 in CS table.
*/
  
update [FIMSynchronizationService].[dbo].[mms_connectorspace]
set [anchor] = cast(reverse(cast(len(mv.uid)*2 as binary(4))) as binary(4))+cast(mv.uid as varbinary(800))
from [FIMSynchronizationService].[dbo].[mms_connectorspace] cs
 join [FIMSynchronizationService].[dbo].[mms_csmv_link] l
  on cs.object_id = l.cs_object_id 
 join [FIMSynchronizationService].[dbo].[mms_metaverse] mv
  on mv.object_id = l.mv_object_id
where cs.ma_id = '2F2516F6-AD5B-4CFA-9F2B-AA4385D1879E' and cs.[anchor] is NULL
-

Issue 3

When you perform an export run for ECMA 2.0 Export Only MA, you receive the following error message:

The image or delta doesn’t have an anchor.

-

Forefront Identity Manager Connector for SharePoint User Profile Store

Issue 1

The SharePoint connector fails when there are multiple user profile services.

-

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

(2015-03-16) Documenting Your Active Directory

Posted by Jorge on 2015-03-16


Have you ever wanted to document your Active Directory to quite some detail, but you could not find a free tool available to get the job done? Wait no longer! Keep reading, you will like it!

-

Amongst other scripts, Carl Webster has created an Active Directory documentation PowerShell script that is able to procedure a report in Word, PDF or HTML. The script must be executed on a Windows computer with at least PowerShell v3, RSAT and Word. In general you just require a regular user account with no specific privileges. However, if you want to gather DC specific information (e.g. hardware and services), you require Domain Admins equivalent privileges which is obvious.

More information about the AD documentation script can be found through the link: Documenting Microsoft Active Directory with Microsoft Word and PowerShell

All the scripts can be downloaded through the link: Where to Get Copies of the Various Documentation Scripts

-

In my test/demo environment I executed the AD documentation PowerShell script using the following PowerShell command line:

.\ADDS_Inventory_v1.1.ps1 -PDF -Hardware -ADForest IAMTEC.NET -Hardware -Services -UserName "Jorge de Almeida Pinto" -CompanyName "IAM Technologies" -Verbose

image

Figure 1: Sample Output

-

image

Figure 2: Sample Output

-

image

Figure 3: Sample Output

-

image

Figure 4: Sample Output

-

To view a sample of the report you can click here. It displays all the information about my test/demo AD environment.

-

Be aware, that if your AD is large and/or distributed (e.g. many AD domains, many DCs, many OUs, many objects, etc.), this documentation script may take some time to complete!

-

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), Documentation, PowerShell, Tooling/Scripting | 1 Comment »

(2015-03-12) Resolving The "Policy Violation" Error With FIM SSPR

Posted by Jorge on 2015-03-12


You may be testing SSPR or a user may actually be using it to reset its own password and the following error is presented.

image

Figure 1: The Password Does Not Comply With Your Organization’s Password Policies

-

When you look in the Forefront Identity Manager Event Log you will see the following event ID 3

image

Figure 2: The Password Reset Activity In The Password Reset Action Workflow Failed Due To A Policy Violation

-

PWReset Activity’s MIIS Password Set call failed because of a policy violation.

-

…And you will also see the following error, which does not tell you anything

image

Figure 3: Service Fault Exception – DataRequiredFaultReason

-

The web portal received a fault error from the FIM service.
Details:
Microsoft.ResourceManagement.WebServices.Faults.ServiceFaultException: DataRequiredFaultReason
   at Microsoft.ResourceManagement.WebServices.ResourceFactoryClient.Create(Message request)
   at Microsoft.IdentityManagement.CredentialManagement.Portal.Common.ResetProxy.InteractWithPasswordResetActivity(SecureString newPassword, String activityEndpoint, String workflowInstanceId, ContextualSecurityToken sessionSecurityToken)
Web Portal: FIM Password Reset Portal
Session Id: xlei5mqvkukke145sjxbu355
IP Address: 10.1.1.32

-

A password policy consists of the following policy settings:

  1. Enforce password history X passwords remembered
  2. Maximum password age X days
  3. Minimum password age X days
  4. Minimum password length X characters
  5. Password must meet complexity requirements Disabled
  6. Store passwords using reversible encryption

-

A password change will always enforce all policy settings, except policy setting [2] (The maximum password age is what actually triggers the password change)

-

A regular password reset or a password reset through FIM SSPR while policy enforcement is disabled will by default enforce all policy settings, except the policy settings [2], [1] and [3]. When policy enforcement is enabled as specified in "FIM 2010 Self Service Password Reset now supports Enforcement of all domain password policies", it will also enforce policy setting [1] and [3], and therefore the password reset will behave like a password change. Be aware of that!

-

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) Portal, Self Service Password Reset | Leave a Comment »

(2015-03-08) Resolving The "PWUnrecoverableError" Error With FIM Self-Service Password Reset (SSPR)

Posted by Jorge on 2015-03-08


While using or configuring FIM 2010 (R2) SSPR you might receive the error after specifying the new password twice and submitting . This of course needs troubleshooting, but looking at the generic error and code you need to dig further to really understand what went wrong.

image

Figure 1: Error 3000 After Specifying A New Password Twice And Submitting It

-

Looking at the Forefront Identity Manager Event Log you will may find Event ID 3 with an "Access is denied" statement. Now you know something is wrong with authentication or authorization between the FIM Service and the FIM Sync Service.

image

Figure 2: Unauthorized Access Exception – Access is denied

-

mscorlib: System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Management.ManagementScope.InitializeGuts(Object o)
   at System.Management.ManagementScope.Initialize()
   at System.Management.ManagementObjectSearcher.Initialize()
   at System.Management.ManagementObjectSearcher.Get()
   at Microsoft.ResourceManagement.PasswordReset.ResetPassword.ResetPasswordHelper(String domainName, String userName, String newPasswordText)

-

You may also find the following Event ID 2 in the Forefront Identity Manager Event Log.

image

Figure 3: Workflow Terminated Exception

-

System.Workflow.ComponentModel.WorkflowTerminatedException: Exception of type ‘System.Workflow.ComponentModel.WorkflowTerminatedException’ was thrown.

-

You may also find the following Event ID 3 in the Forefront Identity Manager Event Log.

image

Figure 4: Service Fault Exception

-

The web portal received a fault error from the FIM service.
Details:
Microsoft.ResourceManagement.WebServices.Faults.ServiceFaultException: DataRequiredFaultReason
   at Microsoft.ResourceManagement.WebServices.ResourceFactoryClient.Create(Message request)
   at Microsoft.IdentityManagement.CredentialManagement.Portal.Common.ResetProxy.InteractWithPasswordResetActivity(SecureString newPassword, String activityEndpoint, String workflowInstanceId, ContextualSecurityToken sessionSecurityToken)
Web Portal: FIM Password Reset Portal
Session Id: ufoifqmbyuwt0p3cim0iz455
IP Address: 10.1.1.32

-

You will also find the following Event ID 3 in the Forefront Identity Manager Event Log.

image

Figure 5: HTTP Unhandled Exception – PWUnrecoverableError

-

Microsoft.IdentityManagement.CredentialManagement.Portal: System.Web.HttpUnhandledException: ScriptManager_AsyncPostBackError —> System.InvalidProgramException: Error while performing the password reset operation: PWUnrecoverableError
   at Microsoft.IdentityManagement.CredentialManagement.Portal.Reset.AttemptToResetPassword()
   at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
   at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   — End of inner exception stack trace —
   at Microsoft.IdentityManagement.CredentialManagement.Portal.Site.ScriptManager_AsyncPostBackError(Object sender, AsyncPostBackErrorEventArgs eventArgs)
   at System.Web.UI.ScriptManager.OnAsyncPostBackError(AsyncPostBackErrorEventArgs e)
   at System.Web.UI.PageRequestManager.OnPageError(Object sender, EventArgs e)
   at System.Web.UI.TemplateControl.OnError(EventArgs e)
   at System.Web.UI.Page.HandleError(Exception e)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
   at System.Web.UI.Page.ProcessRequest()
   at System.Web.UI.Page.ProcessRequest(HttpContext context)
   at ASP.default_aspx.ProcessRequest(HttpContext context)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously

-

You will also find the following Event ID 3 in the Forefront Identity Manager Event Log.

image

Figure 6: Invalid Program Exception – PWUnrecoverableError

-

The error page was displayed to the user.
Details:
Title: Error
Message: An error has occurred. Please try again, and if the problem persists, contact your help desk or system administrator. (Error 3000)
Source:
Attributes:
Details: System.InvalidProgramException: Error while performing the password reset operation: PWUnrecoverableError
   at Microsoft.IdentityManagement.CredentialManagement.Portal.Reset.AttemptToResetPassword()
   at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
   at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument)
   at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument)
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
CorrelationId:
RequestId:
ErrorCode: 3000
CaughtTime: 01/09/2015 21:47:39

Web Portal: FIM Password Reset Portal
Session Id: ufoifqmbyuwt0p3cim0iz455
IP Address: 10.1.1.32

-

If you have tracing enabled for the FIM Service you would also see the following. To enable tracing for the FIM Service see the following blog post "(2013-11-01) Advanced Logging, Event Tracing Or Troubleshooting Within FIM Components". Basically you comment the default "Default Diagnostics configuration" and remove the comment from the "Advanced Diagnostics Configuration (Full Diagnostics configuration)" and restart the FIM service. By the way: DO NOT forget to disable tracing afterwards!!!

image

Figure 7: Unauthorized Access Exception – Access is denied

-

Microsoft.ResourceManagement Verbose: 0 : WQL:SELECT * FROM MIIS_CSObject WHERE (Domain=’IAMTEC’ AND Account=’John.Doe’) or (FullyQualifiedDomain=’IAMTEC’ AND Account=’John.Doe’) or (Domain=’IAMTEC’ AND UserPrincipalName=’John.Doe’) or (FullyQualifiedDomain=’IAMTEC’ AND UserPrincipalName=’John.Doe’)
    ThreadId=16
    DateTime=2015-01-09T20:47:38.5549208Z
Microsoft.ResourceManagement Error: 3 : mscorlib: System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
   at System.Management.ManagementScope.InitializeGuts(Object o)
   at System.Management.ManagementScope.Initialize()
   at System.Management.ManagementObjectSearcher.Initialize()
   at System.Management.ManagementObjectSearcher.Get()
   at Microsoft.ResourceManagement.PasswordReset.ResetPassword.ResetPasswordHelper(String domainName, String userName, String newPasswordText)
    ThreadId=16
    DateTime=2015-01-09T20:47:38.5705433Z

-

In this case the "PWUnrecoverableError" error is related to unauthorized access. In other words, the FIM Service account is lacking some permission somewhere. To be sure NOTHING is missed, check everything listed below.

-

[1] Permissions In The FIM Sync Engine

The password reset is done through the FIM Sync Engine, therefore the FIM Service must have the permission to look up the connector space object and actually set the new password. For that to be possible the FIM Service account must be a member of the "FIM Sync Browse Group" and the "FIM Sync Password Set Group". See below as an example. If you just added the FIM Service account to these groups, make sure to restart the FIM Service!

image

Figure 8: Required FIM Sync Group Memberships For The FIM Service Account When Using SSPR

-

[2] DCOM Permissions

The FIM Service account requires the correct DCOM permissions on the FIM Sync Server(s). I say server(s) because you might have a running FIM Sync server and a hot or cold standby FIM Sync server. The manual steps to enable DCOM for the FIM Service account on the FIM Sync Server(s) are:

  1. Log on to the FIM Sync Server with local administrative permissions
  2. Click Start, click Administrative Tools, and then click Component Services.
  3. On Component Services, expand Component Services, and then expand Computers.
  4. Right-click My Computer, and then click Properties.
  5. On My Computer Properties, click COM Security.
  6. On COM Security, under Access Permissions, click Edit Limits.
  7. On Access Permissions, click Add.
  8. On Select, Users, Computers, and Groups, in the Enter the object names to select (examples) box, enter the FIM Service account name, and then click Check Name.
    When the service account name resolves successfully, it appears underlined.
  9. Click OK.
  10. On Access Permissions, select the FIM Service account name and place a check in the Allow check box for both Local Access and Remote Access.
  11. Click OK.
  12. On COM Security, under Access Permissions, click Edit Default.
  13. On Access Permissions, click Add.
  14. On Select, Users, Computers, and Groups, in the Enter the object names to select (examples) box, enter the FIM Service account name, and then click Check Name.
    When the service account name resolves successfully, it appears underlined.
  15. Click OK.
  16. On Access Permissions, select the FIM Service account name and place a check in the Allow check box for both Local Access and Remote Access.
  17. Click OK.
  18. On COM Security, under Launch and Activation Permissions, click Edit Limits.
  19. On Launch and Activation Permissions, click Add.
  20. On Select, Users, Computers, and Groups, in the Enter the object names to select (examples) box, enter the FIM Service account name, and then click Check Name.
    When the service account name resolves successfully, it appears underlined.
  21. Click OK.
  22. On Launch and Activation Permissions, select the FIM Service account name and place a check in the Allow check boxes for Local Launch, Remote Launch, Local Activation, and Remote Activation.
  23. Click OK.
  24. On COM Security, under Launch and Activation Permissions, click Edit Default.
  25. On Access Permissions, click Add.
  26. On Select, Users, Computers, and Groups, in the Enter the object names to select (examples) box, enter the FIM Service account name, and then click Check Name.
    When the service account name resolves successfully, it appears underlined.
  27. Click OK.
  28. On Launch and Activation Permissions, select the FIM Service account name and place a check in the Allow check boxes for Local Launch, Remote Launch, Local Activation, and Remote Activation.
  29. Click OK.
  30. On My Computer Properties, click Apply, and then click OK.
  31. Close Component Services.

-

If you think, "damn, that’s a lot of work!", you can also use PowerShell to do it!. See Using PowerShell to Set DCOM Permissions for FIM Self-Service Password Reset

SNAGHTML5c47cfcd

Figure 9: DCOM Permissions – Access Permissions – Edit Limits

-

SNAGHTML5c48e0ee

Figure 10: DCOM Permissions – Access Permissions – Edit Default

-

SNAGHTML5c4b49ff

Figure 11: DCOM Permissions – Launch And Activation Permissions – Edit Limits

-

SNAGHTML5c4bae86[4]

Figure 12: DCOM Permissions – Launch And Activation Permissions – Edit Default

-

[3] WMI Permissions

The FIM Service account requires the correct WMI permissions on the FIM Sync Server(s). I say server(s) because you might have a running FIM Sync server and a hot or cold standby FIM Sync server. The manual steps to enable WMI for the FIM Service account on the FIM Sync Server(s) are:

  1. Log on to the FIM Sync Server with local administrative permissions.
  2. Click Start, select Administrative Tools, and click Computer Management.
  3. In Computer Management, expand Configuration, right-click WMI Controls and select Properties.
  4. Click the Security tab.
  5. Expand Root, select CIMV2, and then click the Security button. This will bring up the Security for ROOT\CIMV2.
  6. On Security for ROOT\CIMV2, click Add.
  7. On Select Users, Computers, and Groups, in the Enter the object names to select (examples) box, enter the FIM Service account name, and then click Check Name.
    When the service account name resolves successfully, it appears underlined.
  8. Click OK.
  9. On Security for ROOT\CIMV2, for the FIM Service account name ensure that Allow is selected for both Enable Account and Remote Enable.
  10. Click Advanced. This will bring up the Advanced Security Settings for CIMV2.
  11. On Advanced Security Settings for CIMV2, select the FIM Service account name and then click Edit. This will bring up Permission Entry for CIMV2.
  12. On Permission Entry for CIMV2, select This namespace and subnamespaces in the Apply To box.
  13. Click OK.
  14. On Advanced Security Settings for CIMV2, click Apply, and then click OK.
  15. On Security for ROOT\CIMV2, click OK.
  16. On WMI Control Properties, click OK.
  17. Close Computer Management.

-

If you think, "damn, that’s a lot of work!", you can also use PowerShell to do it!. See How to Use PowerShell to Set WMI Permissions for FIM Self-Service Password Reset or Using PowerShell to Set WMI Permissions for FIM Self-Service Password Reset.

image

Figure 13: WMI Permissions – ROOT\COMV2

-

If you have all of the above (already) correctly configured and it is still not working, you need to check something check to see if that’s correctly configured. Open the System Event Log and see if you can find the warning Event ID 6037 similar to the one shown below.

image

Figure 14: LSA Error Regarding Incorrect SPN Configuration

-

The program svchost.exe, with the assigned process ID 860, could not authenticate locally by using the target name RPCSS/FIMSYNC.IAMTEC.NET. The target name used is not valid. A target name should refer to one of the local computer names, for example, the DNS host name.
 
Try a different target name.

-

If you do find the above warming, then navigate to the folder "C:\Program Files\Microsoft Forefront Identity Manager\2010\Service" on the server with the FIM Service installed and open the file "Microsoft.ResourceManagement.Service.exe.config" and look for the string "synchronizationServerName". After finding it, look for its value, which in this case is "FIMSYNC.IAMTEC.NET". Ask yourself if the value is the real FQDN of the FIM Sync Server or if it is an alias for the FIM Sync Server. In this case the specified FQDN is an alias registered in DNS as an A record, not a CNAME record. If the value specified is neither, then you need to run a change install of the FIM Service to be able to specify the correct FQDN of the FIM Sync Server. I really suggest you use an alias for the FIM Sync Server as it gives you lots of flexibility, especially if you have a running FIM Sync Server and a hot/cold standby of the FIM Sync Server. If you would not use an alias but the real FQDN of the FIM Sync Server, and you have multiple FIM Service server instances, you would need to do a change install on each and every FIM Service server instance just to change the FIM Sync Server FQDN. With an alias you do not need to do that, you just need to change the DNS record.

image

Figure 15: The FIM Sync Server FQDN Specified In the FIM Service Configuration File

-

If the value specified is the real FQDN of the FIM Sync Server, then you need to make sure the following SPNs are registered in the servicePrincipalNames attribute on the computer account of the FIM Sync Server:

  • HOST/<NetBIOS Name FIM Sync Server> (e.g. HOST/R1FSMBSV2)
  • HOST/<FQDN Name FIM Sync Server>(e.g. HOST/R1FSMBSV2.IAMTEC.NET)

-

In this case you need to check for the HOST SPN and not the RPCSS SPN, as the RPCSS SPN is covered by the HOST SPN. You can check this mapping in the "sPNMappings" in the object "CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration,DC=IAMTEC,DC=NET"

If any of the HOST SPN is missing, add it back, but be aware for duplicate SPNs!

-

If the value specified is the alias FQDN of the FIM Sync Server, then you need to make sure the following SPNs are registered in the servicePrincipalNames attribute on the computer account of the FIM Sync Server:

  • RPCSS/<NetBIOS Alias FIM Sync Server> (e.g. RPCSS/FIMSYNC)
  • RPCSS/<FQDN Alias FIM Sync Server>(e.g. RPCSS/FIMSYNC.IAMTEC.NET)

-

image

Figure 16: Registering The RPCSS SPN For The Alias On The Computer Account Of The FIM Sync Server

-

When using an alias FQDN for the FIM Sync Server, you need to make sure that alias is registered in DNS as an A record and not as a CNAME record! The downside of this approach is that if you need to activate your hot/cold standby, you also need to move the RPCSS SPN from the computer account of the previous FIM Sync Server to the computer account of the new FIM Sync Server. To make you do not have that dependency, make sure you have the following:

  • HOST/<NetBIOS Name FIM Sync Server> registered on the computer account of any FIM Sync Server (should be there by default!)
  • HOST/<FQDN Name FIM Sync Server> registered on the computer account of any FIM Sync Server (should be there by default!)
  • RPCSS/<NetBIOS Alias FIM Sync Server> NOT registered anywhere
  • RPCSS/<FQDN Alias FIM Sync Server> NOT registered anywhere
  • The Alias FQDN is registered in DNS as a CNAME record and mapped to the real FQDN of the running FIM Sync Server

-

So this last approach have a downside? Yes it does! The downside of this approach is that if you need to activate your hot/cold standby, you also need to change the CNAME DNS record mapping to the FQDN of the new FIM Sync Server.

-

Whatever you choose, it should work. You just need to decide which approach you prefer.

-

After all this, you should not experience the "PWUnrecoverableError" Error anymore!

-

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) Portal, Self Service Password Reset | 1 Comment »

(2015-03-04) Clearing The MV Object ID Of All Groups In The Portal

Posted by Jorge on 2015-03-04


I had used the script from Carol Wapshere to duplicate FIM Portal objects (groups). With that duplication the MV Object ID was also copied. Therefore from a metaverse perspective I had duplicate objects (multiple objects with the same MV Object ID). When importing those objects into FIM sync you get the error:

image

Figure 1: Ambiguous Import Errors For Specific Objects Against Objects In The Metaverse

-

image

Figure 2: Ambiguous Import Error For A Specific Object

-

Because of the duplicate MV Object ID on FIM Portal objects, these (multiple) objects were trying to join to a single MV object instead of projecting new MV objects. The duplicated objects should project into the metaverse instead! The solution to achieve that was to clear the MV Object ID for every group object in the FIM Portal. To get that done I wanted/needed to use PowerShell.

-

I used the FIM PowerShell Function Library (FIM-PowerShell-Function-Library-Custom.ps1) as described here. Then I created the following simple script

-

Clearing the MV Object ID of all groups

Add-PSSnapin FIMAutomation
. .\FIM-PowerShell-Function-Library-Custom.ps1
$objectsInFIMToProcess = Export-FIMConfig -Uri $URI –OnlyBaseResources -CustomConfig "/Group"
$objectsInFIMToProcessPSObject = $objectsInFIMToProcess | Convert-FimExportToPSObject
$objectsInFIMToProcessPSObject | %{
    $objectToProcess = $($_.ObjectID).TrimStart("urn:uuid:")
    $objectToProcessPrepared = $objectToProcess | Prepare-FimObject $_.ObjectType
    $objectToProcessPreparedWithChanges = $objectToProcessPrepared | Set-FimAttribute -AttributeName "MVObjectID" -AttributeValue $null
    $objectToProcessPreparedWithChangesToImport = $objectToProcessPreparedWithChanges | Commit-FimObject
}

-

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) Portal, PowerShell, Tooling/Scripting | 1 Comment »

(2015-02-28) FIM Fails With "Unable To Create New WorkflowInstance For WorkflowDefinition"

Posted by Jorge on 2015-02-28


Currently my AD/ADFS/FIM environment was running on one server including all other required software components such as Exchnage, SQL and Sharepoint. I wanted a more flexible configuration so I decided to split up the different roles and reinstalled the environment including everything in it. Configurations were either redone or migrated. In the case of FIM I migrated the configuration using the known migration tools/scripts/methods. At the same time I also reconfigured a few things (e.g. Workflows) in FIM. Because of the additional changes I tested the worflows that were impacted as those were using custom activities. Everything appeared to be fine! The FIM Portal in the new environment was running on Sharepoint Foundation 2013 and with regards to FIM the latest available build at the time of writing was being used.

-

To uniquely identity every object in the FIM Portal an wanted to assign a global ID to group and user objects. User objects already had that so I only needed to configure stuff for groups. For this task I was using OCG’s Function Evaluator to generate a GUID. As I already had that activity configured somewhere I decided to copy that specific configuration and put it in a new workflow which I configured with "Run On Policy Update (ROPU). Then I created a set and a transition based MPR. I disabled the MPR, committed that and re-enabled it again. Thinking to be done very quickly I checked all requests and I expected to see only completes. WRONG I only saw tons of PostProcessingErrors as shown below.

image

Figure 1: PostProcessing Errors For Requests

-

Looking at the one of the System Event Request

image

Figure 2: PostProcessing Errors For Requests

-

Looking at the event viewer

image

Figure 2: "Unable To Create New WorkflowInstance For WorkflowDefinition" Error In The "Forefront Event Viewer" Event Viewer Log

-

Microsoft.ResourceManagement: Microsoft.ResourceManagement.Workflow.Hosting.WorkflowManagerException: Unable to create new WorkflowInstance for WorkflowDefinition ‘cb6b64a3-9785-4dfe-aab2-5b4e8338eee2′. —> System.Workflow.ComponentModel.Compiler.WorkflowValidationFailedException: The workflow failed validation.
   at System.Workflow.Runtime.Hosting.DefaultWorkflowLoaderService.CreateInstance(XmlReader workflowDefinitionReader, XmlReader rulesReader)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.LoadRootActivity(String xomlText, String rulesText, Byte[] xomlHashCode, Boolean createDefinition, Boolean initForRuntime)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.MruCache.GetOrGenerateDefinition(Type type, String xomlText, String rulesText, Byte[] md5Codes, Boolean initForRuntime, Boolean& exist)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.GetRootActivity(String xomlText, String rulesText, Boolean createNew, Boolean initForRuntime)
   at System.Workflow.Runtime.WorkflowRuntime.InitializeExecutor(Guid instanceId, CreationContext context, WorkflowExecutor executor, WorkflowInstance workflowInstance)
   at System.Workflow.Runtime.WorkflowRuntime.Load(Guid key, CreationContext context, WorkflowInstance workflowInstance)
   at System.Workflow.Runtime.WorkflowRuntime.GetWorkflowExecutor(Guid instanceId, CreationContext context)
   at System.Workflow.Runtime.WorkflowRuntime.InternalCreateWorkflow(CreationContext context, Guid instanceId)
   at System.Workflow.Runtime.WorkflowRuntime.CreateWorkflow(XmlReader workflowDefinitionReader, XmlReader rulesReader, Dictionary`2 namedArgumentValues, Guid instanceId)
   at Microsoft.ResourceManagement.Workflow.Hosting.WorkflowManager.StartWorkflowInstance(Guid workflowInstanceIdentifier, KeyValuePair`2[] additionalParameters)
   — End of inner exception stack trace —
   at Microsoft.ResourceManagement.Utilities.ExceptionManager.ThrowException(Exception exception)
   at Microsoft.ResourceManagement.Workflow.Hosting.WorkflowManager.StartWorkflowInstance(Guid workflowInstanceIdentifier, KeyValuePair`2[] additionalParameters)

-

After seeing this error, I tried an already existing workflow using the same function evaluator and the same activity configuration. That worked perfectly, therefore there was nothing wrong with the AIC configuration of the activity and the corresponding DLLs.

-

Because the previous errors did not help in any way and to see if I could get more information I decided to enable tracing for the FIM Service as described through the following blog post "(2013-11-01) Advanced Logging, Event Tracing Or Troubleshooting Within FIM Components".

Basically you comment the default "Default Diagnostics configuration" and remove the comment from the "Advanced Diagnostics Configuration (Full Diagnostics configuration)" and restart the FIM service. Then retrigger the workflow. By the way: DO NOT forget to disable tracing afterwards!!!

-

REMARK: make sure to specify a path where the log files should be created!!!

-

In the trace log ("Microsoft.ResourceManagement.Service_tracelog.txt") I saw the following:

    ThreadId=4
    DateTime=2014-12-14T19:18:50.2133779Z
Microsoft.ResourceManagement Error: 3 : Microsoft.ResourceManagement: Microsoft.ResourceManagement.Workflow.Hosting.WorkflowManagerException: Unable to create new WorkflowInstance for WorkflowDefinition ‘cb6b64a3-9785-4dfe-aab2-5b4e8338eee2′. —> System.Workflow.ComponentModel.Compiler.WorkflowValidationFailedException: The workflow failed validation.
   at System.Workflow.Runtime.Hosting.DefaultWorkflowLoaderService.CreateInstance(XmlReader workflowDefinitionReader, XmlReader rulesReader)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.LoadRootActivity(String xomlText, String rulesText, Byte[] xomlHashCode, Boolean createDefinition, Boolean initForRuntime)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.MruCache.GetOrGenerateDefinition(Type type, String xomlText, String rulesText, Byte[] md5Codes, Boolean initForRuntime, Boolean& exist)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.GetRootActivity(String xomlText, String rulesText, Boolean createNew, Boolean initForRuntime)
   at System.Workflow.Runtime.WorkflowRuntime.InitializeExecutor(Guid instanceId, CreationContext context, WorkflowExecutor executor, WorkflowInstance workflowInstance)
   at System.Workflow.Runtime.WorkflowRuntime.Load(Guid key, CreationContext context, WorkflowInstance workflowInstance)
   at System.Workflow.Runtime.WorkflowRuntime.GetWorkflowExecutor(Guid instanceId, CreationContext context)
   at System.Workflow.Runtime.WorkflowRuntime.InternalCreateWorkflow(CreationContext context, Guid instanceId)
   at System.Workflow.Runtime.WorkflowRuntime.CreateWorkflow(XmlReader workflowDefinitionReader, XmlReader rulesReader, Dictionary`2 namedArgumentValues, Guid instanceId)
   at Microsoft.ResourceManagement.Workflow.Hosting.WorkflowManager.StartWorkflowInstance(Guid workflowInstanceIdentifier, KeyValuePair`2[] additionalParameters)
   — End of inner exception stack trace —

…….

Microsoft.ResourceManagement Verbose: 0 : Entered RequestDispatcher with Request Object; RequestIdentifier ’80cf1669-6933-4047-9db2-9c8032d95177′.
    ThreadId=12
    DateTime=2014-12-14T19:18:54.9869166Z
Microsoft.ResourceManagement Verbose: 0 : Add request ’80cf1669-6933-4047-9db2-9c8032d95177′ to cache with RequestStatus ‘Committed’.
    ThreadId=12
    DateTime=2014-12-14T19:18:54.9869166Z
Microsoft.ResourceManagement Information: 1 : RequestDispatcher enter processing pipeline;  RequestIdentifier ’80cf1669-6933-4047-9db2-9c8032d95177′; Operation ‘SystemEvent'; Object ‘Group'; RequestStatus ‘Committed’.
    ThreadId=12
    DateTime=2014-12-14T19:18:54.9869166Z
Microsoft.ResourceManagement Verbose: 0 : RequestDispatcher is processing RequestIdentifier ’80cf1669-6933-4047-9db2-9c8032d95177′ for a ‘SystemEvent’ operation on object ‘Group’ with RequestStatus ‘Committed’.
    ThreadId=12
    DateTime=2014-12-14T19:18:55.1744221Z
Microsoft.ResourceManagement Verbose: 0 : Request ’80cf1669-6933-4047-9db2-9c8032d95177′ status was updated in-memory from ‘Committed’ to ‘PostProcessing’.
    ThreadId=12
    DateTime=2014-12-14T19:18:55.1744221Z
Microsoft.ResourceManagement Verbose: 0 : Request ’80cf1669-6933-4047-9db2-9c8032d95177′ updates have been persisted to permanent storage.
    ThreadId=12
    DateTime=2014-12-14T19:18:57.5766555Z
Microsoft.ResourceManagement Error: 3 : WorkflowManager could not deserialize XOML definition: ‘<ns0:SequentialWorkflow ActorId="00000000-0000-0000-0000-000000000000" RequestId="00000000-0000-0000-0000-000000000000" x:Name="SequentialWorkflow" TargetId="00000000-0000-0000-0000-000000000000" WorkflowDefinitionId="00000000-0000-0000-0000-000000000000" xmlns:ns1="clr-namespace:T4FIM.FunctionEvaluator;Assembly=T4FIM.FunctionEvaluator, Version=4.0.0.2, Culture=neutral, PublicKeyToken=1cff8ccc43c5c5ec" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow&quot; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; xmlns:ns0="clr-namespace:Microsoft.ResourceManagement.Workflow.Activities;Assembly=Microsoft.ResourceManagement, Version=4.1.3613.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
    <ns1:WorkflowPart CurrentRequest="{x:Null}" readTarget_Resource="{x:Null}" Destination="globalEmployeeID" DestinationType="Target" ControlTitle="Set GlobalEmployeeID" FunctionText="Replace(Right(Left(GUID(),37),36),&quot;-&quot;,&quot;&quot;)" WorkflowEnabled="True" resolveGrammarActivity_ResolvedExpression="{x:Null}" readReferenceAndAttributes_Resource="{x:Null}" LogMessage="GlobalEmployeeID" x:Name="authenticationGateActivity1" resolveGrammarActivity_GrammarExpression="{x:Null}">
        <ns2:ReceiveActivity.WorkflowServiceAttributes xmlns:ns2="clr-namespace:System.Workflow.Activities;Assembly=System.WorkflowServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
            <ns2:WorkflowServiceAttributes ConfigurationName="OCG.Workflow" Name="Workflow" />
        </ns2:ReceiveActivity.WorkflowServiceAttributes>
    </ns1:WorkflowPart>
</ns0:SequentialWorkflow>’.
    ThreadId=12
    DateTime=2014-12-14T19:18:57.6702055Z
Microsoft.ResourceManagement Information: 1 : 347 :  : Invalid Element ‘ReceiveActivity.WorkflowServiceAttributes’ found while deserializing an object of type ‘T4FIM.FunctionEvaluator.WorkflowPart’.
    ThreadId=12
    DateTime=2014-12-14T19:18:57.6702055Z
Microsoft.ResourceManagement Information: 1 : 347 :  : Invalid data found while deserializing an object of type ‘T4FIM.FunctionEvaluator.WorkflowPart’.
    ThreadId=12
    DateTime=2014-12-14T19:18:57.6702055Z
Microsoft.ResourceManagement Error: 3 : Microsoft.ResourceManagement.Workflow.Hosting.WorkflowManagerException: Unable to create new WorkflowInstance for WorkflowDefinition ‘cb6b64a3-9785-4dfe-aab2-5b4e8338eee2′. —> System.Workflow.ComponentModel.Compiler.WorkflowValidationFailedException: The workflow failed validation.
   at System.Workflow.Runtime.Hosting.DefaultWorkflowLoaderService.CreateInstance(XmlReader workflowDefinitionReader, XmlReader rulesReader)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.LoadRootActivity(String xomlText, String rulesText, Byte[] xomlHashCode, Boolean createDefinition, Boolean initForRuntime)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.MruCache.GetOrGenerateDefinition(Type type, String xomlText, String rulesText, Byte[] md5Codes, Boolean initForRuntime, Boolean& exist)
   at System.Workflow.Runtime.WorkflowDefinitionDispenser.GetRootActivity(String xomlText, String rulesText, Boolean createNew, Boolean initForRuntime)
   at System.Workflow.Runtime.WorkflowRuntime.InitializeExecutor(Guid instanceId, CreationContext context, WorkflowExecutor executor, WorkflowInstance workflowInstance)
   at System.Workflow.Runtime.WorkflowRuntime.Load(Guid key, CreationContext context, WorkflowInstance workflowInstance)
   at System.Workflow.Runtime.WorkflowRuntime.GetWorkflowExecutor(Guid instanceId, CreationContext context)
   at System.Workflow.Runtime.WorkflowRuntime.InternalCreateWorkflow(CreationContext context, Guid instanceId)
   at System.Workflow.Runtime.WorkflowRuntime.CreateWorkflow(XmlReader workflowDefinitionReader, XmlReader rulesReader, Dictionary`2 namedArgumentValues, Guid instanceId)
   at Microsoft.ResourceManagement.Workflow.Hosting.WorkflowManager.StartWorkflowInstance(Guid workflowInstanceIdentifier, KeyValuePair`2[] additionalParameters)
   — End of inner exception stack trace —

-

Based upon my experience, when I see something like "deserializing", I know something is wrong with the XOML definition of the new workflow. The question is WHY, as another workflow with the same configuration did work! Because of I decided to compare the XOML definition of workflow that worked and the one that failed

-

XOML Definition for working WFW

<ns0:SequentialWorkflow x:Name="SequentialWorkflow" ActorId="00000000-0000-0000-0000-000000000000" WorkflowDefinitionId="00000000-0000-0000-0000-000000000000" RequestId="00000000-0000-0000-0000-000000000000" TargetId="00000000-0000-0000-0000-000000000000" xmlns:ns1="clr-namespace:T4FIM.FunctionEvaluator;Assembly=T4FIM.FunctionEvaluator, Version=4.0.0.2, Culture=neutral, PublicKeyToken=1cff8ccc43c5c5ec" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow&quot; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; xmlns:ns0="clr-namespace:Microsoft.ResourceManagement.Workflow.Activities;Assembly=Microsoft.ResourceManagement, Version=4.1.3508.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">

    <ns1:WorkflowPart x:Name="authenticationGateActivity16" WorkflowEnabled="True" CurrentRequest="{x:Null}" FunctionText="Replace(Right(Left(GUID(),37),36),&quot;-&quot;,&quot;&quot;)" resolveGrammarActivity_GrammarExpression="{x:Null}" ControlTitle="Set GlobalEmployeeID" LogMessage="GlobalEmployeeID" readTarget_Resource="{x:Null}" readReferenceAndAttributes_Resource="{x:Null}" resolveGrammarActivity_ResolvedExpression="{x:Null}" Destination="globalEmployeeID" DestinationType="Target">
        <ns2:ReceiveActivity.WorkflowServiceAttributes xmlns:ns2="clr-namespace:System.Workflow.Activities;Assembly=System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
            <ns2:WorkflowServiceAttributes Name="Workflow" ConfigurationName="OCG.Workflow" />
        </ns2:ReceiveActivity.WorkflowServiceAttributes>
    </ns1:WorkflowPart>

</ns0:SequentialWorkflow>

-

XOML Definition for not working WFW

<ns0:SequentialWorkflow ActorId="00000000-0000-0000-0000-000000000000" RequestId="00000000-0000-0000-0000-000000000000" x:Name="SequentialWorkflow" TargetId="00000000-0000-0000-0000-000000000000" WorkflowDefinitionId="00000000-0000-0000-0000-000000000000" xmlns:ns1="clr-namespace:T4FIM.FunctionEvaluator;Assembly=T4FIM.FunctionEvaluator, Version=4.0.0.2, Culture=neutral, PublicKeyToken=1cff8ccc43c5c5ec" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/workflow&quot; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; xmlns:ns0="clr-namespace:Microsoft.ResourceManagement.Workflow.Activities;Assembly=Microsoft.ResourceManagement, Version=4.1.3613.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
    <ns1:WorkflowPart CurrentRequest="{x:Null}" readTarget_Resource="{x:Null}" Destination="globalEmployeeID" DestinationType="Target" ControlTitle="Set GlobalEmployeeID" FunctionText="Replace(Right(Left(GUID(),37),36),&quot;-&quot;,&quot;&quot;)" WorkflowEnabled="True" resolveGrammarActivity_ResolvedExpression="{x:Null}" readReferenceAndAttributes_Resource="{x:Null}" LogMessage="GlobalEmployeeID" x:Name="authenticationGateActivity1" resolveGrammarActivity_GrammarExpression="{x:Null}">
        <ns2:ReceiveActivity.WorkflowServiceAttributes xmlns:ns2="clr-namespace:System.Workflow.Activities;Assembly=System.WorkflowServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
            <ns2:WorkflowServiceAttributes ConfigurationName="OCG.Workflow" Name="Workflow" />
        </ns2:ReceiveActivity.WorkflowServiceAttributes>
    </ns1:WorkflowPart>
</ns0:SequentialWorkflow>

-

The differences are highlighted in yellow and green. The yellow difference is taken care of with redirect bindings in the "Microsoft.ResourceManagement.Service.exe.config" file. Instead of updating every workflow, the redirect binding is updated. So that’s not the problem.

The green difference is rather weird! Why is the working workflow using .NET version 3.5.0.0 and why is the not working working workflow using .NET 4.0.0.0?

After changing the XOML definition of the not working workflow from .NET version 4.0.0.0 to 3.5.0.0 the workflow started working again! Yeah!

-

After fixing the version, I reconfigured the XOML definition through the Normal View. It failed again. Damn! Looking at the XOML definition it reverted back to 4.0.0.0. WTF! This appeared to happen for every workflow with custom activities. Apparently every time I now edit a workflow through the normal View, I need to recheck the XOML definition through the Advanced View to make sure the .NET version is correctly configured to 3.5.0.0. This just makes me sad, as for sure this is forgotten to be checked and stuff breaks again.

-

So if you workflow suddenly stops working and throws a similar error this could be the reason!

-

After changing the .NET version in the XOML definition, committing that, disabling the MPR and re-enabling it, everything worked again!

image

Figure 3: Different Request Statuses While The FIM Service Is Executing The ROPU Enabled Workflow

-

image

Figure 4: Completed Request Status After The FIM Service Has Executed The ROPU Enabled Workflow

-

YEAH!

-

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) Portal, Troubleshooting, Uncategorized, Workflow | 1 Comment »

(2015-02-24) Migrating ADFS Databases From SQL To WID

Posted by Jorge on 2015-02-24


For whatever reason you may be interested in migrating your ADFS databases, currently hosted on WID, to SQL server. Information regarding that can be read through the following links:

-

Now some of you might think: "is it possible to migrate the ADFS databases, currently hosted on SQL server, to WID?"

-

Now why would you want to do that?

  • SQL features related to ADFS are not being used (Token Replay Prevention and Artifact Resolution)
  • Saving costs on SQL server licenses
  • Simplifying DR and high availability for ADFS

-

Now to answer the questions…NO, it is not possible to migrate ADFS databases from SQL server to WID!

-

I tried this myself. I did a backup of the ADFS databases on SQL server and then tried to restore those same databases on WID.

image

Figure 1: SQL Management Studio Connected To WID (Local) and SQL Server (Remote)

-

As soon as you try to do the restore, you will see an error similar to the following

image

Figure 2: Failing To Restore A Database Previously Hosted On SQL Server To WID

-

Now why is this? The simple answer is: WID basically uses an older version of SQL than SQL server itself. You can restore a database from a lower version of SQL to a higher version of SQL, but you CANNOT restore a database from a higher version of SQL to a lower version of SQL!

You can read more about this here.

-

The only way to go from SQL to WID is to export all the settings/configurations from ADFS on SQL and import that again into ADFS on WID. Also see: https://jorgequestforknowledge.wordpress.com/2014/03/12/additional-powershell-scripts-for-migrating-adfs-v2-x-to-adfs-v3-0/

-

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), DB On SQL, DB On WID, Uncategorized, Upgrading | 2 Comments »

(2015-02-20) Finding Attributes Configured To Be Never Audited

Posted by Jorge on 2015-02-20


When an attribute is defined with the bit 2^8 (=256) in the searchFlags property, the attribute is configured to be never audited. When “Directory Services Access” is enabled AND the correct SACL has been configured on objects for a specific action on the attribute by specific actors, access (read or write) those attributes will be audited by default. When “Directory Services Changes” is enabled AND the correct SACL has been configured on objects for a specific action on the attribute by specific actors, the values (before and after the change) of those attributes will also be audited by default. To disable the behavior of also auditing values for one or more attributes, those attributes can be configured not to be change audited at all. This might be of interest for those attributes that have been configured as confidential and auditing has been enabled on the object.

-

ADFIND

ADFIND -h R1FSRWDC1.IAMTEC.NET -schema -f "(&(objectClass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=256))" -dn

OR

ADFIND -h R1FSRWDC1.IAMTEC.NET -bit -schema -f "(&(objectClass=attributeSchema)(searchFlags:AND:=256))" -dn

-

image

Figure 1: Example Output

-

AD PoSH Module

Get-ADObject -Server R1FSRWDC1.IAMTEC.NET -SearchBase $((Get-ADRootDSE).schemaNamingContext) -LDAPFilter "(&(objectClass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=256))" | %{$_.DistinguishedName}

-

ADSI Through PoSH

$targetDC = "R1FSRWDC1.IAMTEC.NET"
$rootDSE = [ADSI]"LDAP://$targetDC/RootDSE"
$schemaNamingContext = $rootDSE.schemaNamingContext
$search = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$Search.SearchRoot = "LDAP://$targetDC/$schemaNamingContext"
$search.filter = "(&(objectClass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=256))"
$search.FindAll() | %{$_.Properties.distinguishedname}

-

PS: replace the FQDN of the DC with your info

-

PS: the opposite of this query can be found by replacing (searchFlags:1.2.840.113556.1.4.803:=256) with (!(searchFlags:1.2.840.113556.1.4.803:=256))

-

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), AD Queries, Schema, Tooling/Scripting | Leave a Comment »

 
%d bloggers like this: