1.877.833.1202

Use Powershell to automate configuration tasks

Wednesday, June 26, 2019

Early in the lifecycle of a new FileHold installation, we often find customers who have a list of dropdown menu options they would like to add that is too large to be practical to add manually as a FileHold managed list, but too small to warrant maintaining in an external database. They could use our configuration import tool, but that might be too cumbersome if there is just a single field that needs updating.

Powershell and the FileHold API to the rescue. If we have a text file with a list of our options, perhaps we could add them as simply as by using a single command line.

PS> Get-Content -Path .\MyOptions.txt | Add-DropdownValues -FieldName ‘My Field’

Get-Content is a standard Powershell cmdlet for reading text files and we will create Add-DropdownValues using standard Powershell scripting and the FileHold API.

In earlier blog articles I showed how Windows Powershell could be used to interact with the FileHold API. Powershell is a great way to go for making quick work of specialized one-time bulk tasks like the one described. The FileHold API documentation tells us that the DocumentSchemaManager service has the necessary calls to get the list of existing dropdown menu options (GetGlobalField) and update the list with new values (ChangeGlobalField).

Fair warning for the remainder of this post you should have some technical skills if you want to try any of these techniques. Anyone can follow along, but regardless of who you are BE SAFE. Try these things out on your test or development server. When you are ready to take them to production make sure you have backups in place, you test thoroughly and be aware that you are performing powerful bulk actions on your precious data.


We will assume for the remainder of this description that you have logged on with a library administration role or higher and you have added the session id to the FHLSID cookie in your web proxy. Since we are using a different web service than the prior post let’s assume we have created the basic proxy as follows.

$dsm = New-WebServiceProxy -Uri http://myserver/fh/filehold/LibraryManager/DocumentSchemaManager.aspx?WSDL -Namespace DocumentSchemaManager

You may note that the -Namespace parameter is new from the prior posts and we need it for something a little more magical we will need to do with the proxy later.

With that setup, we can talk about the high-level view of what we need to do with the FileHold API to make this work. Step one is to get the id for the metadata field we want to add the new values to. We need this because, as with virtually everything in FileHold, the action takes place based on internal ids. Step two is to use the id to get the current state of the metadata field as we want to maintain the configuration as is, just add some new values. Finally, we need to call the change method after adding our list of new values to the existing configuration. Simple, right!

The next piece of the puzzle is a bit of Powershell magic called the pipeline. The one-liner I presented at the beginning is an example of how you use the pipeline. Basically, one command’s output becomes the next commands input. Get-Content was reading a file and outputting each line one at a time. Add‑DropdownValue was simply looking for one value at a time from the pipeline. In fact, we can skip the Get-Content part all together and provide the values directly.

‘Apple’, ‘Orange’, ‘Banana’ | Add-DropdownValues -FieldName Fruit

The point is there are lots of places we can get the input from if our Add-DropdownValues is able to process the pipeline. The key to processing the pipeline is a script structure like the following:

function Add-DropdownValues
{
  [CmdletBinding(SupportsShouldProcess = $true)]
  Param (
    [Parameter(
        ValueFromPipeline = $true
    )]    [Object]$ValueList,
    [Parameter(
        Mandatory = $true
    )]
    [string]$FieldName
  )
  Begin {}
  Process {}
  End {}
}

The SupportsShouldProcess directive is important to set the processing mode. The ValueFromPipeline directive will put the data from the pipeline into a variable of our choosing. The remainder of the structure is broken into three sections: begin, process and end. Begin operates before the pipeline data is provided, Process is called each time pipeline data is made available to us. Generally, this is once for each value in the pipeline. Finally, End is there as a place for us to perform any necessary processing before the command finishes.

In our case, we need to use the Begin section to log in and prepare our web proxy, but we also need it to match the field name we will provide on the command line with the metadata field names configured in the system and get the internal id. We will do this with a call to the GetFieldsOverviewList method.

$metadataFieldsList = $dsm.GetFieldsOverviewList()

Now we can get the specific field we care about with a simple search.

$metadataFieldInfo = $metadataFieldsList.Where( { $_.Name -eq $FieldName } )

That’s it. Well, we skipped a few things that we might want to add here. The whole reason for making these calls in the begin section is so we could do some error checking in case the field does not exist, it’s not a dropdown menu, etc. I’ll leave that for you to try on your own.

The only other thing we should do is prepare a variable to store the new dropdown menu values temporarily.

$choices = New-Object -TypeName System.Collections.ArrayList -ArgumentList ( $null )

We use an ArrayList as it has a little less overhead than a native Powershell array for cases where we have no idea how many times we will increasing the size of the list.

Now we are ready for our Process section. Here we are going to prepare a loop for flexibility as it is possible our cmdlet could be called with the pipeline as planned or the options could be provided directly using the -ValueList parameter in which case we will not get them one at a time. The following loop works perfectly with either case.

foreach ( $Value in $ValueList ) {}

The next thing we need to do is take the value provided in the pipeline and convert it to a variable of the type DropdownFieldChoice. This is where the namespace we mentioned previously becomes important. We are going to need a bit of trickery to find the object we need to create to match the WSDL that our web service proxy is expecting. If we did not specify that specific namespace we would find that Powershell would make one up for us and that would be messy to use.

$field = New-Object -TypeName DocumentSchemaManager.DropdownFieldChoice

Now that we have the object for the menu item we can assign our new value to its value property and add the object to our array of choices. Note that the add method of the ArrayList returns the position where the object was added to the list, but we do not care so we assign it to $null so it does not become output of our script.

$field.Value = $Value
$null = $choices.Add( $field )

As the pipeline provides us with each value, we add it to our list.

The last thing to do is save the results in our metadata field in the End section. Remember the $metadataFieldInfo we collected in the Begin section. Now we will use it to get the actual field we want to update.

$metadataField = $dsm.GetGlobalField( $metadataFieldInfo.Id )

With the existing field in hand we want to make sure we include the existing dropdown values at the beginning of our $choices list.

$choices.InsertRange( 0, $metadataField.Choices )

Now we have a complete list and can assign it back to the choices in our metadata field.

$metadataField.Choices = $choices

The next thing we need to do is trick the Powershell XML serializer. There are two read only fields in the metadata field object: Schemas and SchemasUsedIn. Now, Schemas is an array and the serialization/deserialization process works fine detecting correctly that it has no elements. However, SchemasUsedIn is a string and Powershell sees it as a null string as opposed to a null object. As a result, it will pass an empty string to FileHold and FileHold will throw an error unless we trick it a bit.

$metadataField.SchemasUsedIn = [NullString]::Value

Finally, we can update the old field configuration with our new field configuration include the newly added dropdown values.

$dsm.ChangeGlobalField( $metadataField, $false )

ChangeGlobalField will analyze the contents of your choice list. When it sees a choice object with an existing id and the same name, it will do nothing. When there is an id of zero, like our new choices, it will add the new value. If it does not see an existing item, it will assume it should be deleted.

That’s really it! We can log in with the normal client and check that our metadata field now has a new list of values from our pipeline.


At the links below, I have provided a complete solution including some error checking. You can use the ‑WhatIf parameter to run the cmdlet without updating your FileHold system. You can include the ‑InformationAction Continue parameter to give some feedback during processing and the ‑Verbose parameter to provide a bit of a trace of what is happening in the background. You want to put both ps1 files in the same folder if you want them to run without changes. Both are signed.

You may want to have a close look at FileHoldApi.ps1 as I have added some features to simplify writing Powershell to use the FileHold API in a wider range of cases. First off, the script maintains a cache of logged in users. That’s right, you can log in more than once with different users in the same Powershell script. This can be useful for cases where you want the various FileHold logs to reflect operations from your bulk changes in a certain order as you can switch users on-the-fly. It also checks to see if the user sessions are still valid before you use them. To take advantage of this, FileHold services have been turned into functions that automatically know how to switch which user is working at any given time by swapping cookies.

Instead of $dsm.GetFieldOverviewList you can use (DocumentSchemaManager).GetFieldOverviewList.

Add-DropdownValues.ps1
FileHoldApi.ps1

Russ Beinder portrait Russ Beinder is the Chief Technology Officer at FileHold. He is an entrepreneur, a seasoned business analyst, computer technologist and a certified Project Management Professional (PMP). For over 30 years he has used computer technology to help organizations solve business problems.