I wrote this collection of tools to make it easier to manage views for Orion.  Run the ViewImporter in a powershell window and it will walk you through some prompts to connect to your Orion instance and it will ask you to point it to a set of folders created by the ViewExporter tool, ViewExporter.ps1 .  Then it parses the XML files in the directories and creates the relevant views or it can add widgets to, or over-write the existing version of that view if you like. Ideally these tools will allow dashboards and widgets to be shared between users to try and develop an ecosystem of views and widgets here on Thwack.

A single widget/resource importer tool can be accessed here, ResourceImporter.ps1

You can grab the first example dashboard I shared and import it for your environment here, SolarWinds Admin

I consider this to be a major improvement in usability over the earlier version of the concept I had posted before, viewmigrator.txt

I've been using it pretty regularly myself for some time and haven't been having bugs for a while so I feel like its ready to share.  *Thanks to everyone who has been risking their environments by letting me test it on them over the past year emoticons_wink.png


Tested with Solarwinds platform 2018.2 through 2019.4

Powershell 5 - I think the only thing I used that will cause problems with earlier versions is that the transcription option I used was not available in earlier releases.  If you get errors relating to "-IncludeInvocationHeader" then you can ignore them, it just won't be logging the screen output.


Upon launch is will prompt you for your Orion server


Then it needs to know the admin credentials to use within Orion, if your windows credentials are also an admin in Orion then you can enter Trusted to pass through using those, otherwise you enter Explicit to get a logon prompt.




At this point press enter and it will pop open a folder selection dialogue.  ***Sometimes this comes in behind the powershell window and you won't see it until you minimize your session***


Navigate down to the folder where your individual view or view group is saved.  The script will be looking for a file called viewdata.xml, if it can't find that then it knows you have not selected a directory with the correct contents.


If you pick the correct folder it will read the contents of the viewdata.xml which sets the parameters for the view itself in terms of the columns and width and icons and such.  Then it iterates through the resource XML files which contain all the resources/widgets and their properties.


Once it has read in all the resources and tabs for the view it will assemble them into a to do list where it first checks if there is already a view with the same title and, if applicable, view group name.  If there are no conflicting matches it will create a new view/viewgroup and fill in all the necessary widgets with no further requirements from you.


If there is a conflict it will prompt you letting you know the viewid it found and asking if you want to Add these widgets to the existing view, if we should replace the existing view, or just quit altogether.


After each view there is some cleanup I had to do in SQL to handle some special characters that the widgets could contain that would have broken the XML. 

Once the view or group is imported it will ask you if you need to select another file to import or if you want to quit.

Once you quit it will write a transcript of the entire execution either to your desktop or the folder where the .ps1 file was executed from.

The attached file is actually just a link to the GitHub repo where I keep these kinds of things, so that way we can ensure that you are always using the most current version in case I have to fix issues that may come up in newer releases.

This is the actual text of the current version if you can't get to GitHub for some reason

<#------------- FUNCTIONS -------------#>

Function Set-SwisConnection {


  [Parameter(Mandatory=$true, HelpMessage = "What SolarWinds server are you connecting to (Hostname or IP)?" ) ] [string] $solarWindsServer,

  [Parameter(Mandatory=$true, HelpMessage = "Do you want to use the credentials from PowerShell [Trusted], or a new login [Explicit]?" ) ] [ ValidateSet( 'Trusted', 'Explicit' ) ] [ string ] $connectionType,

  [Parameter(HelpMessage = "Which credentials should we use for an explicit logon type" ) ] $creds


  IF ( $connectionType -eq 'Trusted' ) {

  $swis = Connect-Swis -Trusted -Hostname $solarWindsServer

  } ELSEIF(!$creds) {

  $creds = Get-Credential -Message "Please provide a Domain or Local Login for SolarWinds"

  $swis = Connect-Swis -Credential $creds -Hostname $solarWindsServer

  } ELSE {

  $swis = Connect-Swis -Credential $creds -Hostname $solarWindsServer


  RETURN $swis


<#------------- ACTUAL SCRIPT -------------#>


$now = Get-Date -Format "yyyyMMdd_HHmm"

$script = $MyInvocation.MyCommand

if($script.path){ $dir = Split-Path $script.path }

else { $dir = [Environment]::GetFolderPath("Desktop") }

$Logfile = "$dir\$($$now.log"

Start-Transcript -Path $Logfile -Append -IncludeInvocationHeader

while(!$swistest) {

  $hostname = Read-Host -Prompt "what server should we connect to?"

  $connectionType = Read-Host -Prompt "Should we use the current powershell credentials [Trusted], or specify credentials [Explicit]?"

  $swis = Set-SwisConnection $hostname $connectionType

  $swistest = get-swisdata $swis "SELECT TOP 1 servername FROM Orion.Websites"


"Connected to $hostname Successfully using $connectiontype credentials"

$quit = $null

while ($quit -ne "Quit" ) {

  "`nPlease provide the folder to import"

  $quit = Read-Host 'Press Enter to select file to import, or type [Quit] to exit'

  switch -regex ($quit) {

  "quit" { "`n`nQuitting"; $quit="Quit" ; break}

  default {

  $inputfolder = $null

  $inputfolder = New-Object -Typename System.Windows.Forms.FolderBrowserDialog


  $inputfolder = $inputfolder.selectedpath

  "$inputfolder selected..."

  $isView = test-path ("$inputfolder\Viewdata.xml")

  $importedViews = @()

  if($isview -eq $false) {

  $folders = Get-ChildItem -Path $inputfolder -directory

  } else {

  $folders = "\"


  foreach($folder in $folders) {

  $isView = test-path ("$inputfolder\$($\Viewdata.xml")

  if ($isView -eq $false) {

  " Folder contains invalid data, missing $inputfolder\$($\ViewData.xml"

  } else {

  $viewdata = Import-Clixml -path ("$inputfolder\$($\ViewData.xml")

  $resources = Get-ChildItem -Path "$inputfolder\$($" | Where-Object {$ -like "*.xml" -and $ -notlike "viewdata.xml"}

  "`nThis folder contains the view $($viewdata.Viewgroupname) : $($viewdata.viewtitle) `nand the following resources`n"


  if($viewData.viewgroupname -eq "NoViewGroup") { $viewData.viewgroupname = $null;}

  $importedresources = @()

  foreach ($resource in $resources) {

  $resourceproperties = Import-Clixml ("$inputfolder\$($\$($")

  $Importedresource = New-Object -TypeName PSObject

  Add-Member -InputObject $Importedresource -MemberType 'NoteProperty' -Name 'Name' -value $

  Add-Member -InputObject $Importedresource -MemberType 'NoteProperty' -Name 'resourceproperties' -value $resourceproperties

  $importedresources += $importedResource


  $ImportedView = New-Object -TypeName PSObject

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'viewgroupname' -value $viewdata.viewgroupname

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'ViewKey' -value $viewdata.ViewKey

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'ViewTitle' -value $viewdata.ViewTitle

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'ViewType' -value $viewdata.ViewType

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'ViewGroupPosition' -value $viewdata.ViewGroupPosition

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'ViewIcon' -value $viewdata.ViewIcon

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Columns' -value $viewdata.Columns

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Column1Width' -value $viewdata.Column1Width

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Column2Width' -value $viewdata.Column2Width

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Column3Width' -value $viewdata.Column3Width

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Column4Width' -value $viewdata.Column4Width

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Column5Width' -value $viewdata.Column5Width

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Column6Width' -value $viewdata.Column6Width

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'System' -value $viewdata.System

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Customizable' -value $viewdata.Customizable

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'NOCView' -value $viewdata.NOCView

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'NOCViewRotationInterval' -value $viewdata.NOCViewRotationInterval

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'condition' -value $viewdata.condition

  Add-Member -InputObject $ImportedView -MemberType 'NoteProperty' -Name 'Resources' -value $importedresources

  $importedViews += $ImportedView



  foreach($view in $importedViews) {

  " Checking on $($view.viewgroupname) : $($view.viewtitle)"

  if($view.viewgroupname -eq $null) {

  $existingview = Get-SwisData $swis "select top 1 viewid, uri from orion.views v where v.viewtitle = '$($view.viewtitle)' and (viewgroupname is null or viewgroupname = '')"

  } else {

  $existingview = Get-SwisData $swis "select top 1 viewid, uri from orion.views v where v.viewtitle = '$($view.viewtitle)' and v.viewgroupname = '$($view.viewgroupname)'"


  $viewgroup = Get-SwisData $swis "select distinct viewgroup from orion.views v where v.viewgroupname = '$($view.viewgroupname)'"

  if($viewgroup -notmatch "\d+") {$viewgroup = $null}

  if($view.viewgroupname -ne $null -and $viewgroup -eq $null){

  #generate the next available viewgroup number

  $viewgroup = get-swisdata $swis "select min(viewgroup+1) as Nextgroup from orion.views where viewgroup is not null and viewgroup+1 not in (select distinct viewgroup from orion.views where viewgroup is not null) "


  if(!$existingview) {

  " Creating $($view.Viewgroupname) : $($view.ViewTitle)"

  $viewprops = @{

  "ViewGroup" = $viewgroup

  "ViewGroupName" = $($view.ViewGroupName)

  "Viewkey" = $($view.ViewKey)

  "ViewTitle" = $($view.ViewTitle)

  "ViewType" = $($view.ViewType)

  "ViewGroupPosition" = $($view.ViewGroupPosition)

  "ViewIcon" = $($view.ViewIcon)

  "Columns" = $($view.Columns)

  "Column1Width" = $($view.Column1Width)

  "Column2Width" = $($view.Column2Width)

  "Column3Width" = $($view.Column3Width)

  "Column4Width" = $($view.Column4Width)

  "Column5Width" = $($view.Column5Width)

  "Column6Width" = $($view.Column6Width)

  "System" = $($view.System)

  "Customizable" = $($view.Customizable)

  "NOCView" = $($view.NOCView)

  "NOCViewRotationInterval" = $($view.NOCViewRotationInterval)


  $newview = New-SwisObject $swis -EntityType "Orion.Views" -properties $viewprops

  $viewid = Get-SwisData $swis "select viewid from orion.views where uri = '$newview'"

  "New view created - $viewid"


  if($existingview) {

  $viewid = $existingview.viewid

  $menu = $null

  while($menu -eq $null) {

  $menu = Read-Host " View already exists as ViewID $viewid, should we [Add] the resouces to the view, [Replace] the contents of the view, or [Quit]?"

  switch -regex ($menu) {

  "quit" { "`n`nQuitting"; $menu="Quit" ; break}

  "replace" {

  $oldresources = get-swisdata $swis "SELECT uri FROM Orion.Resources where viewid = $($existingview.viewid)"

  foreach ($r in $oldresources) {

  " Removing old resources from view $viewid"

  Remove-SwisObject $swis -Uri $r | Out-Null



  "add" { break }

  default {$menu = $null; "Invalid response, please re-enter"; break}




  if($menu -ne "quit") {

  foreach($r in $View.resources) {

  " Adding resource $($r.Name)"

  $resourceResults = Invoke-SwisVerb $swis Orion.Views AddResourceToView @($viewid, $r.ResourceProperties)


  "`nCleaning Up"

  $cleanup = Invoke-SwisVerb $swis 'Orion.Reporting' 'ExecuteSQL' @"

update resourceproperties set propertyvalue = replace(replace(propertyvalue, 'linebreak', char(10)),'ampersand',char(38)) where propertyvalue like '%linebreak%' or propertyvalue like '%ampersand%'

update resources set resourcename = replace(replace(ResourceName,'ampersand',char(38)),'doublequotes',char(34)), resourcetitle = replace(replace(ResourceTitle,'ampersand',char(38)),'doublequotes',char(34)), resourcesubtitle = replace(replace(resourcesubtitle,'ampersand',char(38)),'doublequotes',char(34)) where resourcename like '%ampersand%' or resourcetitle like '%ampersand%' or resourcesubtitle like '%ampersand%' or resourcename like '%doublequotes%' or resourcetitle like '%doublequotes%' or resourcesubtitle like '%doublequotes%'


  if($ImportedView.condition) {

  $sqlcondition = Invoke-SwisVerb $swis 'Orion.Reporting' 'ExecuteSQL' "SELECT condition FROM [dbo].[ViewConditions] where viewid = $viewid"

  if(!$sqlcondition.ChildNodes.documentelement.executesqlresults) {Invoke-SwisVerb $swis 'Orion.Reporting' 'ExecuteSQL' "insert into [dbo].[ViewConditions] values ( $viewid,'$($ImportedView.condition)' )"}

  else {Invoke-SwisVerb $swis 'Orion.Reporting' 'ExecuteSQL' "update [ViewConditions] set condition = '$($ImportedView.condition)' where viewid = '$viewid'" | out-null}









Github - ViewImporter.url