Recently I’m working quite a lot with Project Server 2013. My tasks include – beyond development – creation of methods that supports the continuous delivery of the results from the development environment to the test and production environments. I found that my old friend, PowerShell is an invaluable tool in this field as well.
Recently I had to solve a problem, where we had a rather complex, multi-level lookup table (RBS) on the development server, and we had to transfer the same structure on each deployment to the test server. Typing the same structure via the UI each time would have been a very boring and time consuming activity.
If we export the structure via the UI to Excel,
the result looks like this:
However, when we try to paste the values to the lookup list via the UI, the fields are shifted to the right: the values in the Level field become to the values of the Value field, the Value becomes to the Description, and the original Description is lost, making the whole pasting worthless.
I found a very useful PowerShell script on the blog of Paul Mather (the code is available in the TechNet Script Center as well). This script utilizes the PSI interface, however is limited to a single level of values, no hierarchical lookup tables.
I’ve extended the sample using the generic Stack object of the .NET Framework, pushing and popping the Guids of the parent items, and importing the value of the Description field as well. Otherwise most of the code was borrowed from, and the functionality is identical to the original version of Paul. As input file, a TAB separated text file is used without field name headers, including the Level, Value and Description fields, in the case above, for example:
1 Value 1 Desc 1
2 Value 1_1 Desc 1.1
3 Value 1_1_1 Desc 1.1.1
2 Value 1_2 Desc 1.2
3 Value 1_2_1 Desc 1.2.1
2 Value 1_3 Desc 1.3
This sample is limited to lookup tables with character-based code sequences.
The PowerShell script that enables the mulit-level import:
- #Get lookup table values to add
- $values = Get-Content "C:\Data\PowerShell\RBSValues.txt"
- #Specify Lookup table to update
- $lookupTableName = "RBS"
- $lcid = 1033
- $emptyString = [String]::empty
- $svcPSProxy = New-WebServiceProxy -Uri "http://sp2013/pwa/_vti_bin/PSI/LookupTable.asmx?wsdl" -UseDefaultCredential
- $lookupTableGuid = ($svcPSProxy.ReadLookupTables($emptyString, 0, $lcid).LookupTables | ? {$_.LT_NAME -eq $lookupTableName }).LT_UID
- $lookupTable = $svcPSProxy.ReadLookupTablesbyUids($lookupTableGuid, 1, $lcid)
- #get lookup table count
- $lookuptableValues = $svcPSProxy.ReadLookupTablesbyUids($lookupTableGuid, 0, $lcid).LookupTableTrees
- $count = $lookuptableValues.Count + 1
- #update lookup table…
- $stack = New-Object System.Collections.Generic.Stack[Guid]
- $lastLevel = 1
- $values | % {
- $fields = $_ -split '\t+'
- $level = $fields[0]
- $text = $fields[1]
- $desc = $fields[2]
- $guid = [Guid]::NewGuid()
- # Write-Host Count: $count, text: $text, Guid: $guid, Level: $level, Last level: $lastLevel
- $parentGuid = $lastGuid
- If ($lastLevel -lt $level) {
- $stack.Push($lastGuid)
- # Write-Host Parent GUID Pushed: $parentGuid
- }
- Else {
- While (($stack.Count -ge ($level)) -and ($stack.Count -gt 1)) {
- # Write-Host Popping level ($stack.Count + 1)
- $parentGuid = $stack.Pop()
- # Write-Host Parent GUID Popped: $parentGuid
- }
- If ($stack.Count -gt 0) {
- $parentGuid = $stack.Peek()
- # Write-Host Parent GUID Peeked: $parentGuid
- }
- }
- $LookupRow = $lookuptable.LookupTableTrees.NewLookupTableTreesRow()
- If (-Not [String]::IsNullOrEmpty($desc)) {
- $LookupRow.LT_VALUE_DESC = $desc
- }
- $LookupRow.LT_STRUCT_UID = $guid
- $LookupRow.LT_UID = $lookupTableGuid
- $LookupRow.LT_VALUE_TEXT = $text
- If ($level -gt 1) {
- # Write-Host Parent GUID set: $parentGuid
- $LookupRow.LT_PARENT_STRUCT_UID = $parentGuid
- }
- $LookupRow.LT_VALUE_SORT_INDEX = ($count++)
- $lookuptable.LookupTableTrees.AddLookupTableTreesRow($LookupRow)
- $lastGuid = $guid
- $lastLevel = $level
- }
- $Error.Clear()
- Try
- {
- $svcPSProxy.UpdateLookupTables($lookuptable , 0 , 1 , $lcid)
- }
- Catch
- {
- Write-Host "Error updating the Lookup table, see the error below:" -ForeGroundColor Red -BackGroundColor White
- Write-Host "$error" -ForeGroundColor Red
- }
- If ($Error.Count -eq 0)
- {
- Write-Host "The lookup table $lookupTablename has been updated with the values from the text file specified" -ForeGroundColor Green
- }
- Else
- {
- Write-Host "The lookup table $lookupTablename has not been updated with the values from the text file specified, please see error" -ForeGroundColor Red -BackGroundColor White
- }
- #force checkin in case of failure
- $Error.Clear()
- Try
- {
- $svcPSProxy.CheckInLookUpTables($lookupTableGuid, 1)
- }
- Catch
- {
- If ($error -match "LastError=CICONotCheckedOut")
- {
- }
- Else
- {
- Write-Host "Error checking the Lookup table, see the error below:" -ForeGroundColor Red -BackGroundColor White
- Write-Host "$error" -ForeGroundColor Red
- }
- }
The script includes a lot of Write-Host cmdlets to enable tracking of the process. These are commented in the version above. You are free to either use or delete these lines as you wish.
Note: Don’t forget to alter the file path, the URI and the lookup table name, and the LCID as well, if you are working with a non-English version of PWA.
