Lately I’ve been doing lots of work with logging various FIM-related data to SQL tables and presenting them with SQL Reporting Services (SSRS). I’ve been having some good fun with SSRS – there seems to be a lot you can do with just a basic understanding of queries and parameters – and I’m sure I’m only scratching the surface so far.
The bigger challenge has been to get all the data I want to report on into a reporting database in nice, simple table formats. Some of the things I’m putting into the reporting database: sync run stats, errors and object changes; all completed requests and approvals; the current state of all person and group objects; healthcheck and data quality stats.
I’m not giving away all that work, but I will share this script with you, which includes some useful techniques for picking information out of a FIM object and writing it to a SQL table. In this case the logged objects are recent failed requests.
PARAM($Duration=2) ### ### Log-FailedRequests.ps1 ### ### Run periodically to record failed requests that have occurred in the last $Duration hours. ### - Update $FailedReqsFilter to change the request statuses that are logged. ### - Already logged requests wil be updated in the table, so $Duration can be longer than the run interval. ### ### FIMPowershell.ps1 ### This script uses the FIMPowerShell Function library from http://technet.microsoft.com/en-us/library/ff720152(v=ws.10).aspx ### ### LOGGING TO SQL ### Expects a table with the following design: ### ### CREATE TABLE [dbo].[fim_failedrequests]( ### [ObjectID] [nvarchar](50) NOT NULL, ### [ParentRequest] [nvarchar](50) NOT NULL, ### [RequestTime] [datetime] NULL, ### [Requestor] [nvarchar](250) NULL, ### [Target] [nvarchar](250) NULL, ### [DisplayName] [nvarchar](150) NULL, ### [Status] [nvarchar](50) NULL, ### [Error] [nvarchar](max) NULL, ### CONSTRAINT [PK_fim_requests_errors] PRIMARY KEY CLUSTERED ### ( ### [ObjectID] ASC ### )WITH (PAD_INDEX = OFF;STATISTICS_NORECOMPUTE = OFF;IGNORE_DUP_KEY = OFF;ALLOW_ROW_LOCKS = ON;ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ### ) ON [PRIMARY] ### ### ### CONSTANTS ### $SQLServer = "localhost" $SQLInstance = "Default" $SQLDB = "FIMReporting" $SQLTable = "fim_failedrequests" ### FUNCTIONS . E:\scripts\FIMPowershell.ps1 # FIMDate: Returns the local-time date string as a UTC FIM-formatted date string function FIMDate { PARAM($DateString) END { $DT = Get-Date $DateString $UTCDate = $DT.ToUniversalTime() $ReturnDate = (Get-Date $UTCDate -Format "s") + ".000" $ReturnDate } } ### SQL Connection - SQL PowerShell must be installed locally ### if(@(get-pssnapin | where-object {$_.Name -eq “sqlserverprovidersnapin100â€} ).count -eq 0) {add-pssnapin sqlserverprovidersnapin100} if(@(get-pssnapin | where-object {$_.Name -eq “sqlservercmdletsnapin100â€} ).count -eq 0) {add-pssnapin sqlservercmdletsnapin100} set-location SQLSERVER:\SQL\$SQLServer\$SQLInstance\Databases\$SQLDB ### SQL Queries ### $sqlAddRow = "insert into $SQLTable (ObjectID;ParentRequest;RequestTime;Requestor;Target;DisplayName;Status;Error) VALUES ('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}')" $sqlUpdateRow = "update $SQLTable set Status='{0}';Error='{1}' where ObjectID='{2}'" $EndTime = FIMDate (get-date).ToString() $StartTime = FIMDate (get-date).AddHours(-$Duration).ToString() $FailedReqsFilter = "/Request[CreatedTime > '{0}' and CreatedTime <= '{1}' and (RequestStatus = 'Denied' or RequestStatus = 'Failed' or RequestStatus = 'PostProcessingError')]" -f $StartTime;$EndTime ### MAIN ### ## Export the failed requests with referenced objects so we get the requestor, target and parent request too $objects = Export-FIMConfig -CustomConfig $FailedReqsFilter ## Hash table of display names to log to the table instead of GUIDs $hashNames = @{} $hashNames.Add("urn:uuid:e05d1f1b-3d5e-4014-baa6-94dee7d68c89","Forefront Identity Manager Service Account") $hashNames.Add("urn:uuid:7fb2b853-24f0-4498-9534-4e10589723c4","Administrator") if ($objects) { ## Hash table of Request objects. $Requests = @() foreach ($obj in $objects) { $ObjectID = $obj.ResourceManagementObject.ObjectIdentifier if (-not $hashNames.ContainsKey($ObjectID)) { $DisplayName = ($obj.ResourceManagementObject.ResourceManagementAttributes | where {$_.AttributeName -eq 'DisplayName'}).Value $hashNames.Add($ObjectID,$DisplayName) } if (($obj.ResourceManagementObject.ResourceManagementAttributes | where {$_.AttributeName -eq 'ObjectType'}).Value -eq "Request") {$Requests += $obj} } } if ($Requests) { foreach ($req in $Requests) { $ObjectID = $req.ResourceManagementObject.ObjectIdentifier $hashReq = ConvertResourceToHashtable $req if ($hashReq.ParentRequest) {$Parent = $hashReq.ParentRequest.Replace("urn:uuid:","")} else {$Parent = ''} if ($hashNames.ContainsKey($hashReq.Creator)) {$Creator = $hashNames.Item($hashReq.Creator).Replace("'","''")} else {$Creator = $hashReq.Creator} if ($hashNames.ContainsKey($hashReq.Target)) {$Target = $hashNames.Item($hashReq.Target).Replace("'","''")} else {$Target = $hashReq.Target} if ($hashReq.DisplayName) {$DisplayName = $hashReq.DisplayName.Replace("'","''")} else {$DisplayName = ""} if ($hashReq.RequestStatusDetail) { $ErrorMsg = "" foreach ($rsd in $hashReq.RequestStatusDetail) { [xml]$rd = $rsd $ErrorMsg = $rd.RequestStatusDetail."#text".Replace("'","''") + ";" + $ErrorMsg } } else {$ErrorMsg = ""} ## The following line gets around a problem in non-US environments where SQL reads the incoming date as US even if set to a non-US region $CreatedTime = (get-date (get-date $hashReq.CreatedTime).ToLocalTime() -format "MM/dd/yyyy HH:mm:ss").ToString() $values = @($ObjectID.Replace("urn:uuid:",""), $Parent, $CreatedTime, $Creator, $Target, $DisplayName, $hashReq.RequestStatus, $ErrorMsg) Try { Invoke-SQLCmd ($sqlAddRow -f $values) -SuppressProviderContextWarning -AbortOnError -ErrorAction "SilentlyContinue" } Catch [Exception] { if ($Error[0] -match "Violation of PRIMARY KEY constraint") { Invoke-SQLCmd ($sqlUpdateRow -f $hashReq.RequestStatus,$ErrorMsg,$ObjectID) -SuppressProviderContextWarning } else {Throw $Error[0]} } } }
Carol-
Two thoughts come to mind:
– Why not use UniqueIdentifier columns for all the GUIDs? Along the same line, did you match the string column lengths to their maximums in the FIM Service database?
– For the SQL insert, if you use a parameterized query, you will not have to worry about the injection issue with the single quotes.
Also, if you use the native .Net SQL classes, you don’t have to take a dependency on having SQL PowerShell installed locally