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
}

 

19 Replies to “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. Great post! How would you do this if you wanted to remove or null out a single value? Thanks again!

  12. 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

  13. 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.

  14. Thanks for a great script, this has really helped me. I think the delimiter needs to be changed to a comma rather than a semicolon to get the filter from the csv file.

  15. Hi Carol,
    I am taking a chance in posting here :-), but did see the last activity here was in March. The question I have is around updating the filter attribute of a group in FIM / MIM. I am using a script you wrote a while ago. I keep on getting the error as output by PS:
    “Error = The web service client has encountered the following class of error: SystemConstraint
    Details: Failed Attributes:
    Additional Text Details: The Request contains changes that violate system constraints.”

    This is in the eventlog:
    “Microsoft.ResourceManagement.WebServices.Exceptions.PermissionDeniedException: SystemConstraint
    at Microsoft.ResourceManagement.ActionProcessor.FilteredResourceActionProcessor.FilteredResourceActionProcessHelper.DoPreProcessRequestByAttribute(RequestType request, CreateRequestParameter filterParameter, CreateRequestParameter deferredEvaluationParameter, String targetType)

    I am executing the script with admin rights. And if I update the filter in FIM Portal it works fine.

    I would appreciate it if you can advise or point me in right direction
    Thanks
    Johan

  16. Hi Johan – a couple of idea.

    First I would be making sure that filter is acceptable in MIM – try using it in a search scope. I always disable the OOB MPR that prevents you using attributes in filter rules unless explicitly specified – I want to use any attribute I please!

    Next make sure you’ve properly formatted the filter for the group, including the opening and closing tags.

    The other thing I do when I regularly run into errors like this is to step through the script in ISE and then thoroughly inspect $newGroup.Changes right before importing it – sometimes you can see what the issue is. If it’s not immediately obvious then try copying the value of Filter and putting in a criteria group that exists in the Portal, you can update it directly through the Advanced view.

  17. Carol,

    Thanks for your quick response. To answer your question, yes I can paste the filter in the advanced view and it is accepted. The OOB MPR you mention, what is the name of it
    Thanks
    Johan

  18. Carol,
    I have managed to solve the issue, but first thanks for the advice, sometimes it just helps to talk to someone to get perspective. You won’t believe what the issue was, in my CSV file, I had the complete filter in quotes, I used excel to compile the xpath filter, and because the filter is a string, I never doubted it 🙂
    Removed the quotes, and voila all works now 🙂
    What put me on the wrong track, is the fact that the error indicates something is wrong with permissions
    Anyway, thanks again
    Johan

Leave a Reply

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


*