Using powershell to update FIM Portal objects from a CSV

I’ve just posted this script to the FIM Forum Scriptbox. It helped me do a bulk update of attributes in the FIM Portal that, for various reasons, I didn’t want to export through the Sync Service. I tried to make the script as flexible as possible, so it reads the attribute names from the header row of the CSV. I’ve only tested it wth single-valued string attributes so far.

While developing this script I came across an issue with the Import-FIMConfig cmdlet where it uses a cached copy of the FIM schema (and considering, in retrospect, how long it takes to export the schema, I’m not surprised it does this). If you’ve just added a new attribute to the schema you will need to restart your powershell session. The error I was getting was System.NullReferenceException (forum thread).

Summary

This script will update resources in the FIM Portal using values in a CSV file.

The CSV file must have the following:

  • A header row,
  • The first three columns must be as follows:
    • ObjectType – the resource type name as used in the Portal,
    • IDAttribute – the name of the attribute used to identity the target resource,
    • IDValue – that value of the attribute used to identify the target resource.
  • The remaining columns have the target Attribute Name from the FIM Portal as header.

For example:

ObjectType,IDAttribute,IDValue,Department,JobTitle
Person,Email,carol@myorg.com,IT,Engineer
Person,Email,bob@myorg.com,HR,Advisor

 

Script Code

PARAM($CSVFile,$FIMServer="localhost",$Delimiter=";",$LogFile="ImportCSV-Attributes.log")

function GetAttribute
{
  PARAM($exportObject,[string] $name)
  END
  {
    $attribute = $exportObject.ResourceManagementObject.ResourceManagementAttributes |
        Where-Object {$_.AttributeName -eq $name}
    if ($attribute -ne $null -and $attribute.Value) {
        $attribute.Value
    }
  }
}

function SetAttribute
{
    PARAM($object, $attributeName, $attributeValue)
    END
    {
        $importChange = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportChange
        $importChange.Operation = 1
        $importChange.AttributeName = $attributeName
        $importChange.AttributeValue = $attributeValue
        $importChange.FullyResolved = 1
        $importChange.Locale = "Invariant"
        if ($object.Changes -eq $null) {$object.Changes = (,$importChange)}
        else {$object.Changes += $importChange}
    }
} 

function WriteLog
{
    PARAM($msg)
    END
    {
        Add-Content -Path $LogFile -Encoding ASCII -value $msg
        write-host $msg
    }
}

if (Test-Path $LogFile) {Remove-Item $LogFile}

if(@(get-pssnapin | where-object {$_.Name -eq "FIMAutomation"} ).count -eq 0) {add-pssnapin FIMAutomation}

$URI = "http://" + $FIMServer + ":5725/ResourceManagementService"

# Parse CSV file. Note we're not using import-csv because we don't know what the column headers will be.
$csv = Get-Content $CSVFile
$header = $csv[0].split($Delimiter)
$numcols = $header.length
$rowcount = 1

while ($rowcount -lt $csv.length)
{
  $rowvals = $csv[$rowcount].split($Delimiter)
  $filter = "/" + $rowvals[0] + "[" + $rowvals[1] + "='" + $rowvals[2] + "']"
  WriteLog -msg "Searching on $filter"

  $FIMObject = $null
  $FIMObject = export-fimconfig -uri $URI -customconfig ($filter) -ErrorVariable Err -ErrorAction SilentlyContinue
  if ($FIMObject.length -gt 1) {$FIMObject = $FIMObject[0]}
  $FIMObjectID = GetAttribute $FIMObject "ObjectID"
  $FIMObjectType = GetAttribute $FIMObject "ObjectType"

  if (($FIMObject -eq $null) -or ($FIMObjectID -eq $null))
  {
    WriteLog -msg "  Not found"
  }
  else
  {
    $bUpdateNeeded = $false

    # Create Import object that will update object in FIM
    $importObject = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportObject
    $importObject.ObjectType = $FIMObjectType
    $importObject.TargetObjectIdentifier = $FIMObjectID
    $importObject.SourceObjectIdentifier = $FIMObjectID
    $importObject.State = [Microsoft.ResourceManagement.Automation.ObjectModel.ImportState]::Put

    # Add the attributes
    $colcount = 3
    while ($colcount -lt $rowvals.length)
    {
      $currentVal = $null
      $currentVal = $FIMObject.ResourceManagementObject.ResourceManagementAttributes | where-object {$_.AttributeName -eq $header[$colcount]}

      if ($rowvals[$colcount].length -eq 0)
      {
        $message = "  No value to set for " + $header[$colcount]
        WriteLog -msg $message
      }
      elseif (($currentVal -ne $null) -and ($rowvals[$colcount] -eq $currentVal.Value))
      {
        $message = "  Value for " + $header[$colcount] + " is already correct"
        WriteLog -msg $message
      }
      else
      {
        $bUpdateNeeded = $true
        $message = "  Setting " + $header[$colcount] + " to " + $rowvals[$colcount]
        WriteLog -msg $message
        SetAttribute -object $importObject -attributeName $header[$colcount] -attributeValue $rowvals[$colcount]
      }
      $colcount += 1
    }

    # Import the changes into FIM
    if ($bUpdateNeeded)
    {
      WriteLog -msg "    Importing changes"
      $importObject | Import-FIMConfig -uri $URI
    }
  }

  $rowcount += 1
}

 

About: Carol

I've been doing IT for 30 years, and IdM for 15. I live in Australia and build IdM solutions based on Microsoft Identity Manager. I also play the violin, but that doesn't help much with the IdM solutions.


14 thoughts on “Using powershell to update FIM Portal objects from a CSV”

  1. Hi Carol,

    Ive created a custom resource type”nzdlrgroup” to which i will be syncing a different set of group attributes to a different AD.

    Ive setup all the new objects/resources in the portal and MV however i want to do a bulk upload of the groups to fim to sync to the AD…unfortunently it doesnt upload…says “OPERATION IS NOT VALID DUE TO THE CURRENT STATE OF THE OBJECT”…

    obviously this is because its a custom object…not a standard ‘group’ object…can you assist with an upload script (will the above work) or some inofo on what i should do to allow me to do bulk uploads on this object?

    I tried above and get a “cannot bind path argument to parameter ‘path’ because it is null’ error.

    appreciate any assistance

    Stu

  2. Hi Stu. Actually it probably isn’t because it’s a custom object class – I’ve done custom objects without any trouble. You need double’check that all the atrributes are spelled correctly in your CSV, that you have MPRs giving you permission to create them, all required attributes set – that sort of thing.

    Sometimes I think I’ve set an attribute and then I really haven’t. So you’re clear on what you need to set, start trying to prepare an import object in Powershell ISE where you can do one step at a time and check the state of the object as you go. Looking at importObject.Changes before importing is often instructive.

  3. Thanks carol

    can you eleborate on exactly what MPRs i need to include the object in or any other settings ill need to check?

    i can add a group to this group object manually but i can put a filter in …it says “unknown error” think it may be a permissions think for an attobute or object?

  4. carol,

    Looks like everything imports except the filter? is their a set or something that needs to give permission?
    i am filtering by another custom ‘person’ object type “nzdlrUser” in my csv…

    e,g:
    /nzdlrUser[(nzdlrDepartmentMV=’SALES’)]

    below is the full script im using…

    #———————————————————————————————————-
    set-variable -name CSV -value “test.csv”
    set-variable -name URI -value “http://fimvsvr:5725/resourcemanagementservice”
    set-variable -name DOMAIN -value “DEALER”
    set-variable -name SCOPE -value “Global”
    set-variable -name TYPE -value “Security”
    set-variable -name OWNER -value “Administrator”
    set-variable -name PREFILTER -value “”
    set-variable -name POSTFILTER -value “”
    #———————————————————————————————————-
    function SetAttribute
    {
    PARAM($object, $attributeName, $attributeValue)
    END
    {
    write-host $attributeName $attributeValue
    $importChange = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportChange
    $importChange.Operation = 1
    $importChange.AttributeName = $attributeName
    $importChange.AttributeValue = $attributeValue
    $importChange.FullyResolved = 1
    $importChange.Locale = “Invariant”
    if ($object.Changes -eq $null) {$object.Changes = (,$importChange)}
    else {$object.Changes += $importChange}
    }
    }
    #———————————————————————————————————-
    function CreateObject
    {
    PARAM($objectType)
    END
    {
    $newObject = New-Object Microsoft.ResourceManagement.Automation.ObjectModel.ImportObject
    $newObject.ObjectType = $objectType
    $newObject.SourceObjectIdentifier = [System.Guid]::NewGuid().ToString()
    $newObject
    }
    }
    #———————————————————————————————————-

    if(@(get-pssnapin | where-object {$_.Name -eq “FIMAutomation”} ).count -eq 0) {add-pssnapin FIMAutomation}

    # Get Owner
    $ownerObject = export-fimconfig -uri $URI `
    –onlyBaseResources `
    -customconfig “/Person[AccountName=’$OWNER’]”
    if($ownerObject -eq $null) {throw “Owner not found!”}
    $ownerID = $ownerObject.ResourceManagementObject.ObjectIdentifier -replace “urn:uuid:”,””

    # Import CSV and process each line
    import-csv($CSV) | foreach {

    # Check if a group with the same name already exists
    $objectName = $_.DisplayName
    $exportObject = export-fimconfig -uri $URI `
    –onlyBaseResources `
    -customconfig “/nzDlrGroup[DisplayName=’$objectName’]”
    if($exportObject) {write-host “`nGroup $objectName already exists”}
    else
    {
    $filter = $PREFILTER + $_.Filter + $POSTFILTER

    # Create group and add attributes
    $newGroup = CreateObject -objectType “nzDlrGroup”
    SetAttribute -object $newGroup -attributeName “DisplayName” -attributeValue $objectName
    SetAttribute -object $newGroup -attributeName “AccountName” -attributeValue $_.AccountName
    SetAttribute -object $newGroup -attributeName “Domain” -attributeValue $DOMAIN
    SetAttribute -object $newGroup -attributeName “Scope” -attributeValue $SCOPE
    SetAttribute -object $newGroup -attributeName “Type” -attributeValue $TYPE
    SetAttribute -object $newGroup -attributeName “Filter” -attributeValue $filter
    SetAttribute -object $newGroup -attributeName “Description” -attributeValue $_.Description
    SetAttribute -object $newGroup -attributeName “Owner” -attributeValue $ownerID
    SetAttribute -object $newGroup -attributeName “DisplayedOwner” -attributeValue $ownerID
    SetAttribute -object $newGroup -attributeName “MembershipLocked” -attributeValue $true
    SetAttribute -object $newGroup -attributeName “MembershipAddWorkflow” -attributeValue “None”

    # Import group into the FIM Portal
    $newGroup | Import-FIMConfig -uri $URI
    write-host “`nGroup creation request complete`n”
    }
    }
    #———————————————————————————————————-
    trap
    {
    $exMessage = $_.Exception.Message
    if($exMessage.StartsWith(“L:”))
    {write-host “`n” $exMessage.substring(2) “`n” -foregroundcolor white -backgroundcolor darkblue}
    else {write-host “`nError: ” $exMessage “`n” -foregroundcolor white -backgroundcolor darkred}
    Exit
    }
    #———————————————————————————————————-

  5. You do not appear to be defining the values for PREFILTER and POSTFILTER. Have a look at my other script on the technet wiki for creating groups from a CSV.

  6. sorry for some reason it didnt stay when i posted it before…as below:

    set-variable -name PREFILTER -value “”
    set-variable -name POSTFILTER -value “”

  7. argh …did it again!

    PRE FILTER: (REMOVED CHARACTERS THAT MAY BE STOPPING IT FROM POSTING)

    Filter xmlns: xsi= http://www.w3.org/2001/XMLSchema-instance xmlns:xsd=`”http://www.w3.org/2001/XMLSchema`” Dialect=`”http://schemas.microsoft.com/2006/11/XPathFilterDialect xmlns=`”http://schemas.xmlsoap.org/ws/2004/09/enumeration`

  8. Here’s the script I mentioned where I uploaded to the group filter from powersghell so I know that can be done: http://social.technet.microsoft.com/wiki/contents/articles/2109.how-to-use-powershell-to-create-criteria-based-security-groups-from-a-csv-file-en-us.aspx

    Have you bound the same underlying filter attribute to your custom object type?

    Another thing to try – create a group manually and copy the exact filter then upload that through powershell. There may be extra error info in the Requests history.

    Also I’m not sure what you are trying to achieve by creating custom person and group object classes, but that is generally considered a bad idea (though should not be effecting your filter).

  9. by creating custom person and group im separating data from mulitple sources into their own objects…
    is there a better way to achieve this?…im working with 3 domains? i dont want data/rules etc mixed up in the MV+portal im trying to keep the environment as clean as possible.

  10. The best practice is: if it is EVER possible for an object to transfer from one “type” to another, then they should have been the same type all along. It is pretty much always better to use the generic types and distinguish between different categories using attributes.

  11. Hi Carol

    I am unable to hit breakpoints that I set in PS or PS ISE when I run a PS activity from Craig Martins PS Activity
    I have the activity running and the PS writes to an output file but I want to walkthrough $fimwf using the PS debugeer

    Any pointers please

  12. Hi Nigel. I haven’t tried attaching a debugger to it, I just stick to verbose logging from my scripts. Any questions about the activity itself should go on its codeplex site.

Leave a Reply

Your email address will not be published. Required fields are marked *


*