├── .gitignore ├── AccessVLANs.swql ├── AlertTemplates └── NodeChange.htm ├── Copy-OrionNode.ps1 ├── CustomQueries └── Node-to-Map_Membership.swql ├── CustomizedActiveAlerts.CustomQueryWidget.swql ├── Delete-UnneededLinuxVolumes.ps1 ├── Disable-OotbAlerts.ps1 ├── ExecuteSQLQuery.ps1 ├── Export-AtlasMapFiles.ps1 ├── Export-Configs.ps1 ├── Export-ConfigsSQL.ps1 ├── Export-DeviceTemplates.ps1 ├── Export-OrionAlerts.ps1 ├── Export-ProductKeys.ps1 ├── Export-SwisCp.ps1 ├── Get-OrionServerInformation.sql ├── Get-OrionServerVersionInformation.ps1 ├── GroupsToCreate.csv ├── LICENSE ├── ListResourcesOnNode.ps1 ├── MutedElements.swql ├── MutedNodes.swql ├── NodeWithIPInfo.CustomQueryWidget.swql ├── Ping-TraceRT.ps1 ├── README.md ├── Remove-InterfacesWithBadIfIndex.ps1 ├── SAM_CheckCertificatesWithExclusion.ps1 ├── SamTemplates └── AmazonRDSTemplate.ps1 ├── Set-EqualColumnWidth.ps1 ├── TopApplicationAvailabilityByTemplate.swql ├── Update-Captions.ps1 ├── Update-OrionServerNodeIds.ps1 ├── UtilityFunctions └── func_Remove-InvalidFileNameChars.ps1 ├── Working.GroupCreation.ps1 ├── Working.GroupCreation.ps1.bak ├── func_ModernDashboards.ps1 └── func_PingTrace.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | # AlertExports 2 | /AlertExports/* 3 | /CustomPropertyExports/* 4 | /DeviceTemplateExports/* 5 | /ModernDashboards/* 6 | /AgentMgmt/ActiveToPassive* 7 | /AgentMgmt/RegisterPassiveAgents.ps1 8 | /_scratch/* 9 | /ExportTests/* -------------------------------------------------------------------------------- /AccessVLANs.swql: -------------------------------------------------------------------------------- 1 | -- The below looks for all interfaces with access VLANs assigned to all devices (no filter on Node Type) 2 | -- It includes a check for a matching VLAN and displays a Red Critical icon, otherwise it shows a Green Check 3 | SELECT [PortMap].Interface.Node.DisplayName AS [Node] 4 | , [PortMap].Interface.Node.DetailsUrl AS [_LinkFor_Node] 5 | , CONCAT('/NetPerfMon/Images/Vendors/', [PortMap].Interface.Node.VendorInfo.Icon) AS [_IconFor_Node] 6 | , [PortMap].Interface.IfName AS [Interface] 7 | , [PortMap].Interface.DetailsUrl AS [_LinkFor_Interface] 8 | , CONCAT('/NetPerfMon/images/Interfaces/', [PortMap].Interface.Icon) AS [_IconFor_Interface] 9 | , [PortMap].VlanId AS [Vlan] 10 | , CASE [PortMap].VlanID 11 | WHEN 2001 THEN '/Orion/images/ActiveAlerts/Critical.png' 12 | ELSE '/Orion/images/ActiveAlerts/Check.png' 13 | END AS [_IconFor_Vlan] 14 | , [PortMap].NodeVlan.VlanName AS [Vlan Name] 15 | FROM Orion.NodePortInterfaceMap AS [PortMap] 16 | WHERE [PortMap].Interface.IfName IS NOT NULL 17 | AND [PortMap].VlanId > 0 18 | -- A PortType of 1 indicates an access port (not a trunk or other) 19 | AND [PortMap].PortType = 1 20 | -- Uncomment the below to use the Search SWQL Query 21 | -- BEGIN SEARCH QUERY 22 | -- AND ( [PortMap].Interface.Node.DisplayName LIKE '%${SEARCH_STRING}%' 23 | -- OR [PortMap].Interface.IfName LIKE '%${SEARCH_STRING}%' 24 | -- OR [PortMap].VlanId = '${SEARCH_STRING}' 25 | -- OR [PortMap].NodeVlan.VlanName LIKE '%${SEARCH_STRING}%' 26 | -- ) 27 | -- END SEARCH QUERY 28 | -- If you want to place this on a Node Details page and have it automatically filter for interfaces on that Node, uncomment the following line 29 | -- AND [PortMap].NodeID = ${NodeID} 30 | ORDER BY [PortMap].Interface.Node.DisplayName 31 | , [PortMap].Interface.IfName 32 | -------------------------------------------------------------------------------- /AlertTemplates/NodeChange.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [Monitoring Event] - ${N=SwisEntity;M=Caption} is ${N=SwisEntity;M=Status} 5 | 101 | 102 | 103 | 104 | 105 | 106 | 146 | 147 | 148 | 149 | 150 | 151 | 157 | 158 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 117 | 118 |
${N=SwisEntity;M=Caption} is ${N=SwisEntity;M=Status}
${N=SwisEntity;M=Caption} is ${N=SwisEntity;M=Status}
Alert Name: ${N=Alerting;M=AlertName} / Trigger Time: 116 | ${N=Alerting;M=AlertTriggerTime;F=DateTime}
119 | 120 | 121 | 143 | 144 |
122 | 123 | 124 | 125 | 127 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 |
Node${N=SwisEntity;M=StatusDescription}${N=SwisEntity;M=Caption} 129 | / ${N=SwisEntity;M=IP_Address}
Node Status ${N=SwisEntity;M=Status}
Application ${N=SwisEntity;M=CustomProperties.PrimaryApplication}
142 |
145 |
Alert Details: ${N=Alerting;M=AlertDescription}
152 |
Acknowledge this Alert 154 | Alert Details 155 |
156 |
159 | 160 | 161 | -------------------------------------------------------------------------------- /Copy-OrionNode.ps1: -------------------------------------------------------------------------------- 1 | # Original Script Source: https://thwack.solarwinds.com/t5/Product-Blog/How-to-automate-the-creation-of-Orion-Platform-aka-Core-nodes/ba-p/447958 2 | 3 | # All that I've done thusfar is clean up some of the calls and formatting from 4 | # the above post's attachment. Overall, it's very good, but hasn't been touched 5 | # in forever. It could use some love and I'll work on it when I can spare time. 6 | 7 | $ErrorActionPreference = 'SilentlyContinue' 8 | 9 | # Set up the hostname, username, and password for the source system 10 | if ( -not ( $SourceSwisConnection ) ) 11 | { 12 | $SourceOrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the source Orion Server" 13 | $SourceSwisCredentials = Get-Credential -Message "Enter your Orion credentials for $SourceOrionServer" 14 | $SourceSwisConnection = Connect-Swis -Credential $SourceSwisCredentials -Hostname $SourceOrionServer 15 | } 16 | 17 | # Set up the hostname, username, and password for the target system 18 | if ( -not ( $TargetSwisConnection ) ) 19 | { 20 | $TargetOrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the target Orion Server" 21 | $TargetSwisCredentials = Get-Credential -Message "Enter your Orion credentials for $TargetOrionServer" 22 | $TargetSwisConnection = Connect-Swis -Credential $TargetSwisCredentials -Hostname $TargetOrionServer 23 | } 24 | 25 | <# removed because this function serves no purpose with modern PowerShell 26 | # we can just check to see if the object exists. 27 | # if it does, then it's not IsEmpty 28 | # ----------------------------------- 29 | # PS C:\> -not '' 30 | # True 31 | # PS C:\> -not $null 32 | # True 33 | # PS C:\> -not "thing" 34 | # False 35 | function IsEmpty($str) { 36 | $str -eq $null -or $str -eq '' -or $str.GetType() -eq [DBNull] 37 | } 38 | #> 39 | 40 | # Define which properties will be copied from the source to the target for Nodes, Interfaces, and Volumes 41 | $NodePropsToCopy = @( "AgentPort", "Allow64BitCounters", "Caption", "ChildStatus", "CMTS", "Community", 42 | "Contact", "DNS", "DynamicIP", "External", "GroupStatus", "IOSImage", 43 | "IOSVersion", "IPAddress", "IPAddressGUID", "IPAddressType", "LastSystemUpTimePollUtc", 44 | "Location", "MachineType", "NodeDescription", "ObjectSubType", "PollInterval", "RediscoveryInterval", 45 | "RWCommunity", "Severity", "SNMPVersion", "StatCollection", "Status", "StatusDescription", 46 | "StatusLED", "SysName", "SysObjectID", "TotalMemory", "UnManaged", "Vendor", "VendorIcon", 47 | "BufferNoMemThisHour", "BufferNoMemToday", "BufferSmMissThisHour", "BufferSmMissToday", "BufferMdMissThisHour", 48 | "BufferMdMissToday", "BufferBgMissThisHour", "BufferBgMissToday", "BufferLgMissThisHour", 49 | "BufferLgMissToday", "BufferHgMissThisHour", "BufferHgMissToday" ) 50 | 51 | $InterfacePropsToCopy = @( "AdminStatus", "AdminStatusLED", "Caption", "Counter64", "CustomBandwidth", "FullName", 52 | "IfName", "InBandwidth", "Inbps", "InDiscardsThisHour", "InDiscardsToday", "InErrorsThisHour", "InErrorsToday", 53 | "InMcastPps", "InPercentUtil", "InPktSize", "InPps", "InterfaceAlias", "InterfaceIcon", "InterfaceIndex", 54 | "InterfaceMTU", "InterfaceName", "InterfaceSpeed", "InterfaceSubType", "InterfaceType", "InterfaceTypeDescription", 55 | "InterfaceTypeName", "InUcastPps", "MaxInBpsToday", "MaxOutBpsToday", "ObjectSubType", "OperStatus", "OutBandwidth", 56 | "Outbps", "OutDiscardsThisHour", "OutDiscardsToday", "OutErrorsThisHour", "OutErrorsToday", "OutMcastPps", 57 | "OutPercentUtil", "OutPktSize", "OutPps", "OutUcastPps", "PhysicalAddress", "PollInterval", "RediscoveryInterval", 58 | "Severity", "StatCollection", "Status", "StatusLED", "UnManaged", "UnPluggable" ) 59 | 60 | $VolumePropsToCopy = @( "Icon", "Index", "Caption", "StatusIcon", "Type", "Size", "Responding", "FullName", 61 | "VolumePercentUsed", "VolumeAllocationFailuresThisHour", "VolumeDescription", "VolumeSpaceUsed", 62 | "VolumeAllocationFailuresToday", "VolumeSpaceAvailable" ) 63 | 64 | # Create the property ImportedByAPI in the remote system 65 | # I 171 | 172 | # Make an in-memory copy of the node 173 | Write-Host "Copying" $SourceNode.Caption "(" $SourceNode.IPAddress ")" 174 | $TargetNodeProps = @{} 175 | $nodePropsToCopy | ForEach-Object { 176 | if ( $SourceNodeProps[$_] ) 177 | { 178 | $TargetNodeProps[$_] = $SourceNodeProps[$_] 179 | } 180 | } 181 | $TargetNodeProps["EngineID"] = $TargetEngineID 182 | 183 | # Create the node on the target system 184 | $NewUri = New-SwisObject -SwisConnection $TargetSwisConnection -EntityType "Orion.Nodes" -Properties $TargetNodeProps 185 | $NewNode = Get-SwisObject -SwisConnection $TargetSwisConnection -Uri $NewUri 186 | 187 | 188 | # Associate the custom property "ImportedByAPI" with this node and set its value to "true" 189 | Set-SwisObject -SwisConnection $TargetSwisConnection -Uri ( "$( $NewUri )/CustomProperties" ) -Properties @{ "ImportedByAPI" = "true" } 190 | 191 | # SNMPv3 credentials are in a sub-object and must be copied separately 192 | if ( $SourceNodeProps.SNMPVersion -eq 3) 193 | { 194 | Write-Host "`tCopying SNMPv3 credentials" 195 | $v3creds = Get-SwisObject -SwisConnection $SourceSwisConnection -Uri ("$( $SourceNode.Uri )/SNMPv3Credentials") 196 | @( "NodeID", "Uri", "InstanceType", "DisplayName", "Description" ) | ForEach-Object { 197 | $v3creds.Remove($_) | Out-Null 198 | } 199 | Set-SwisObject -SwisConnection $Target -Uri ( "$( $newUri )/SNMPv3Credentials") -Properties $v3creds 200 | } 201 | 202 | # Copy the pollers for the new node 203 | $PollerTypes = Get-SwisData -SwisConnection $SourceSwisConnection -Query $SwqlPollers -Parameters @{ "NetObject" = "N:$( $SourceNodeProps.NodeID )" } 204 | 205 | ForEach ($PollerType in $PollerTypes) 206 | { 207 | # Create a new hashtable with the property details 208 | $Poller = @{ 209 | PollerType = $PollerType 210 | NetObject = "N:$( $NewNode.NodeID )" 211 | NetObjectType = "N" 212 | NetObjectID = $NewNode.NodeID 213 | } 214 | Write-Host "`tAdding poller $PollerType" 215 | New-SwisObject -SwisConnection $TargetSwisConnection -EntityType "Orion.Pollers" -Properties $Poller | Out-Null 216 | } 217 | 218 | # Copy interface and volume informaiton from one system to another 219 | # If NPM is installed on both the source and target systems... 220 | if ( $SourceHasNpm -and $TargetHasNpm ) { 221 | # Get the interfaces on the source node 222 | $SourceInterfaces = Get-SwisData -SwisConnection $SourceSwisConnection -Query $SwqlInterfaceUrisByNode -Parameters @{ "NodeID" = $SourceNode.NodeID } 223 | ForEach ( $SourceInterface in $SourceInterfaces ) 224 | { 225 | $SourceIfProps = Get-SwisObject -SwisConnection $SourceSwisConnection -Uri $SourceInterface 226 | Write-Host "`tCopying $( $SourceNode.Caption ) / $( $SourceIfProps.Caption )" 227 | # Build an empty hashtable for the target interface properties 228 | $TargetIfProps = @{} 229 | $InterfacePropsToCopy | ForEach-Object { 230 | # Fill it in from the Source 231 | $TargetIfProps[$_] = $SourceIfProps[$_] 232 | } 233 | $TargetIfProps["NodeID"] = $NewNode.NodeID 234 | 235 | # Create the copy 236 | $NewIfUri = New-SwisObject -SwisConnection $TargetSwisConnection -EntityType "Orion.NPM.Interfaces" -Properties $TargetIfProps 237 | $NewIf = Get-SwisObject -SwisConnection $TargetSwisConnection -Uri $newIfUri 238 | 239 | # Copy the pollers for the new interface 240 | $IfPollerTypes = Get-SwisData -SwisConnection $SourceSwisConnection -Query $SwqlPollers @{ "NetObject" = "I: $( $SourceIfProps.InterfaceID )" } 241 | 242 | ForEach ($ifPollerType in $ifPollerTypes) 243 | { 244 | $IfPoller = @{ 245 | PollerType = $IfPollerType 246 | NetObject = "I:$( $NewIf.InterfaceID )" 247 | NetObjectType = "I" 248 | NetObjectID = $NewIf.InterfaceID 249 | } 250 | Write-Host " Adding poller $ifPollerType" 251 | New-SwisObject -SwisConnection $TargetSwisConnection -EntityType "Orion.Pollers" -Properties $IfPoller | Out-Null 252 | } 253 | } 254 | } 255 | 256 | # Get the volumes on the source node 257 | $SourceVolumes = Get-SwisData -SwisConnection $SourceSwisConnection -Query $SwqlVolumeUrisByNodeID -Paremeters @{ "NodeID" = $SourceNode.NodeID } 258 | ForEach ( $SourceVolume in $SourceVolumes ) { 259 | $SourceVolProps = Get-SwisObject -SwisConnection $SourceSwisConnection -Uri $SourceVolume 260 | Write-Host "`tCopying $( $SourceNode.Caption ) / $( $SourceVolProps.Caption )" 261 | $TargetVolProps = @{} 262 | $VolumePropsToCopy | ForEach-Object { 263 | $TargetVolProps[$_] = $SourceVolProps[$_] 264 | } 265 | $TargetVolProps["NodeID"] = $NewNode.NodeID 266 | 267 | # Create the copy 268 | $NewVolUri = New-SwisObject -SwisConnection $TargetSwisConnection -EntityType "Orion.Volumes" -Properties $TargetVolProps 269 | $NewVol = Get-SwisObject -SwisConnection $TargetSwisConnection -Uri $NewVolUri 270 | 271 | # Copy the pollers for the new Volume 272 | $VolPollerTypes = Get-SwisData -SwisConnection $SourceSwisConnection -Query $SwqlPollers -Parameters @{ "NetObject" = "V:$( $SourceVolProps.VolumeID )" } 273 | 274 | ForEach ($VolPollerType in $VolPollerTypes) { 275 | $VolPoller = @{ 276 | PollerType = $VolPollerType 277 | NetObject = "V:$( $NewVol.VolumeID )" 278 | NetObjectType = "V" 279 | NetObjectID = $NewVol.VolumeID 280 | } 281 | Write-Host "`tAdding poller $VolPollerType" 282 | New-SwisObject -SwisConnection $TargetSwisConnection -EntityType "Orion.Pollers" -Properties $VolPoller | Out-Null 283 | } 284 | } 285 | 286 | # If the target has NCM installed, add the new node to NCM 287 | if ( $TargetHasNCM) 288 | { 289 | Invoke-SwisVerb -SwisConnection $TargetSwisConnection -EntityName "Cirrus.Nodes" -Verb "AddNodeToNCM" -Arguments $NewNode.NodeID 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /CustomQueries/Node-to-Map_Membership.swql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------- 2 | -- Custom Query Widget for Map Membership (Search Capable) 3 | -------------------------------------------------------------- 4 | -- Includes all nodes and what maps they are on (even "none") 5 | -- If you want to enable the search, remove the double-dashes 6 | -- from the WHERE block near the end 7 | -------------------------------------------------------------- 8 | -- Author: KMSigma (https://thwack.solarwinds.com/members/kmsigma) 9 | -------------------------------------------------------------- 10 | -- Inspiration 11 | -- Original Request: https://thwack.solarwinds.com/product-forums/the-orion-platform/f/orion-sdk/97035/report-or-widget-to-identify-any-nodes-not-included-in-any-map/ 12 | -- Related Report: https://thwack.solarwinds.com/content-exchange/the-orion-platform/m/reports/3426 13 | -------------------------------------------------------------- 14 | -- Version History: 15 | -- 1.0.0 / 2022-11-18 - Initial Upload 16 | -------------------------------------------------------------- 17 | 18 | SELECT CASE 19 | WHEN [Members].Container.DisplayName IS NULL 20 | THEN '[None]' 21 | ELSE [Members].Container.DisplayName 22 | END AS [Map] 23 | , CASE 24 | WHEN [Members].Container.DetailsUrl IS NULL 25 | THEN '#' 26 | ELSE [Members].Container.DetailsUrl 27 | END AS [_LinkFor_Map] 28 | , [Nodes].NodeID AS [Node ID] 29 | , [Nodes].Caption AS [Node] 30 | , [Nodes].IPAddress AS [IP] 31 | , [Nodes].DetailsUrl AS [_LinkFor_Node] 32 | , [Nodes].NodeDescription AS [Description] 33 | , [Nodes].VendorInfo.DisplayName AS [Vendor] 34 | , CONCAT ( 35 | '/NetPerfMon/Images/Vendors/' 36 | , [Nodes].VendorInfo.Icon 37 | ) AS [_IconFor_Vendor] 38 | , [Nodes].StatusDescription AS [Status] 39 | , CONCAT ( 40 | '/Orion/images/StatusIcons/Small-' 41 | , [Nodes].StatusIcon 42 | ) AS [_IconFor_Status] 43 | , [Nodes].MachineType AS [Machine Type] 44 | -- This is how you include Node Custom Properties 45 | -- , [Nodes].CustomProperties.Comments 46 | FROM Orion.Nodes AS [Nodes] 47 | LEFT JOIN Orion.ContainerMembers AS [Members] ON [Members].Container.Name LIKE 'MAPS-________-____-____-____-____________' 48 | AND [Members].MemberEntityType = 'Orion.Nodes' 49 | AND [Members].MemberPrimaryID = [Nodes].NodeID 50 | -- Search Options: 51 | -- Uncomment below block to enable search capabilities 52 | -- SEARCH BLOCK: BEGIN 53 | --WHERE ( 54 | -- [Members].Container.DisplayName LIKE '%${SEARCH_STRING}%' 55 | -- OR [Nodes].Caption LIKE '%${SEARCH_STRING}%' 56 | -- OR [Nodes].NodeDescription LIKE '%${SEARCH_STRING}%' 57 | -- OR [Nodes].VendorInfo.DisplayName LIKE '%${SEARCH_STRING}%' 58 | -- OR [Nodes].MachineType LIKE '%${SEARCH_STRING}%' 59 | -- ) 60 | -- SEARCH BLOCK: END 61 | ORDER BY [Members].Container.DisplayName 62 | , [Nodes].Caption 63 | -------------------------------------------------------------------------------- /CustomizedActiveAlerts.CustomQueryWidget.swql: -------------------------------------------------------------------------------- 1 | -- Tested on Orion Platform 2020.2.1 and 2019.4 HF5 2 | -- With input from Marc Netterfield (Mesverrum) [https://github.com/Mesverrum] and Holger Mundt 3 | -- 4 | -- 5 | -- 6 | -- 7 | SELECT DISTINCT [ActiveAlerts].TriggeredMessage AS [Alert] 8 | ,CASE [ActiveAlerts].AlertObjects.AlertConfigurations.Severity 9 | WHEN 0 10 | THEN '/Orion/images/ActiveAlerts/InformationalAlert.png' 11 | WHEN 1 12 | THEN '/Orion/images/ActiveAlerts/Warning.png' 13 | WHEN 2 14 | THEN '/Orion/images/ActiveAlerts/Critical.png' 15 | WHEN 3 16 | THEN '/Orion/images/ActiveAlerts/Serious.png' 17 | WHEN 4 18 | THEN '/Orion/images/ActiveAlerts/Notice.png' 19 | ELSE '/Orion/images/StatusIcons/EmptyIcon.gif' -- we should never get here 20 | END AS [_IconFor_Alert] 21 | ,CONCAT ( 22 | '/Orion/NetPerfMon/ActiveAlertDetails.aspx?NetObject=AAT:' 23 | ,[ActiveAlerts].AlertObjectID 24 | ) AS [_LinkFor_Alert] 25 | -- We can connect to related tables by referencing them. Linked tables are show in SWQL Studio with a chain icon. 26 | -- Here we are chaining base table (ActiveAlerts) to the AlertObjects and selecting additional fields there 27 | ,[ActiveAlerts].AlertObjects.TriggeredCount AS [Count] 28 | ,[ActiveAlerts].TriggeredDateTime AS [Date/Time] 29 | ,CASE 30 | WHEN FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 86400) > 0 31 | THEN TOSTRING(TOSTRING(FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 86400)) + 'd ' + TOSTRING(FLOOR(((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) - 86400 * (FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 86400))) + 0.0) / 3600)) + 'h ' + TOSTRING(FLOOR(((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) - 3600 * (FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 3600))) + 0.0) / 60)) + 'm ') 32 | WHEN FLOOR(((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) - 86400 * (FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 86400))) + 0.0) / 3600) > 0 33 | THEN TOSTRING(TOSTRING(FLOOR(((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) - 86400 * (FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 86400))) + 0.0) / 3600)) + 'h ' + TOSTRING(FLOOR(((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) - 3600 * (FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 3600))) + 0.0) / 60)) + 'm ') 34 | WHEN FLOOR(((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) - 3600 * (FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 3600))) + 0.0) / 60) > 0 35 | THEN TOSTRING(TOSTRING(FLOOR(((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) - 3600 * (FLOOR((SECONDDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) + 0.0) / 3600))) + 0.0) / 60)) + 'm ') 36 | ELSE '' 37 | END AS [Time Active] 38 | ,[ActiveAlerts].AlertObjects.AlertNote AS [Alert Note] 39 | ,CASE 40 | WHEN [ActiveAlerts].Acknowledged IS NULL 41 | THEN '[Acknowledge Now]' 42 | ELSE CONCAT ( 43 | 'By: ' 44 | ,[ActiveAlerts].AcknowledgedBy 45 | ,' at ' 46 | ,[ActiveAlerts].AcknowledgedDateTime 47 | ,' / Note: ' 48 | ,[ActiveAlerts].AcknowledgedNote 49 | ) 50 | END AS [Ack. Details] 51 | ,CASE 52 | WHEN [ActiveAlerts].Acknowledged IS NULL 53 | THEN '/Orion/images/StatusIcons/Small-EmptyIcon.gif' 54 | ELSE '/Orion/images/ActiveAlerts/Acknowliedged_icon16x16v1.png' 55 | END AS [_IconFor_Ack. Details] 56 | ,CASE 57 | WHEN [ActiveAlerts].Acknowledged IS NULL 58 | THEN CONCAT ( 59 | '/Orion/Netperfmon/AckAlert.aspx?AlertDefID=' 60 | ,[ActiveAlerts].AlertObjectID 61 | ) 62 | ELSE CONCAT ( 63 | '/Orion/NetPerfMon/ActiveAlertDetails.aspx?NetObject=AAT:' 64 | ,[ActiveAlerts].AlertObjectID 65 | ) 66 | END AS [_LinkFor_Ack. Details] 67 | ,[ActiveAlerts].AlertObjects.EntityCaption AS [Object] 68 | ,[ActiveAlerts].AlertObjects.EntityDetailsUrl AS [_LinkFor_Object] 69 | -- This part is some magical mystery as far as I'm concerned. I looked at the icons on the actual All Active Alerts page and looked at how the URL was formatted 70 | -- The StatusIcon.ashx takes (for our purposes) four (4) parameters: Entity, EntityUri, Size (always 'small'), and Timestamp 71 | -- Then I just joined them all together to get the icon. 72 | -- The timestamp was the interesting thing because it's in epoch time, which is the number of seconds after 01/01/1970 73 | ,CONCAT ( 74 | '/Orion/StatusIcon.ashx?entity=' 75 | ,[ActiveAlerts].AlertObjects.EntityType 76 | ,'&EntityUri=' 77 | ,[ActiveAlerts].AlertObjects.EntityUri 78 | ,'&size=small×tamp=' 79 | ,SecondDiff('01/01/1970', [ActiveAlerts].TriggeredDateTime) 80 | ) AS [_IconFor_Object] 81 | -- Bring in the related node if it exists 82 | ,CASE 83 | WHEN [ActiveAlerts].AlertObjects.RelatedNodeUri <> [ActiveAlerts].AlertObjects.EntityUri 84 | THEN [ActiveAlerts].AlertObjects.RelatedNodeCaption 85 | ELSE NULL 86 | END AS [On Node] 87 | ,CASE 88 | WHEN [ActiveAlerts].AlertObjects.RelatedNodeUri <> [ActiveAlerts].AlertObjects.EntityUri 89 | THEN [ActiveAlerts].AlertObjects.RelatedNodeDetailsUrl 90 | ELSE NULL 91 | END AS [_LinkFor_On Node] 92 | ,CASE 93 | WHEN [ActiveAlerts].AlertObjects.RelatedNodeUri <> [ActiveAlerts].AlertObjects.EntityUri 94 | THEN CONCAT ( 95 | '/Orion/StatusIcon.ashx?entity=' 96 | ,'Orion.Nodes' 97 | ,'&EntityUri=' 98 | ,[ActiveAlerts].AlertObjects.RelatedNodeUri 99 | ,'&size=small×tamp=' 100 | ,SecondDiff('01/01/1970', [ActiveAlerts].TriggeredDateTime) 101 | ) 102 | ELSE '/Orion/images/StatusIcons/Small-EmptyIcon.gif' 103 | END AS [_IconFor_On Node] 104 | ,(FLOOR(MINUTEDIFF([ActiveAlerts].TriggeredDateTime, GETUTCDATE()) / 60.0)) AS [Active Hours] 105 | FROM Orion.AlertActive AS [ActiveAlerts] 106 | -- We are also chaining here to get the AlertHistory event type /though/ the first chain of AlertObjects 107 | -- The logic is this: "[ActiveAlerts].AlertObjects.AlertHistory." 108 | -- We are querying the Active Alerts, which is connected to AlertObjects, which is connected to AlertHistory 109 | WHERE [ActiveAlerts].AlertObjects.AlertHistory.EventType IN (2, 3) 110 | ORDER BY [ActiveAlerts].TriggeredDateTime DESC -------------------------------------------------------------------------------- /Delete-UnneededLinuxVolumes.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | Simple script to display and then delete unecessary volumes 4 | 5 | #> 6 | 7 | $SwqlQuery = @" 8 | SELECT [Volumes].Node.Caption AS [Node], [Volumes].VolumeDescription, [Volumes].VolumePercentUsed, [Volumes].Uri 9 | FROM Orion.Volumes AS [Volumes] 10 | WHERE [Volumes].VolumePercentUsed > 90 11 | AND [Volumes].VolumeType = 'Other' 12 | AND [Volumes].VolumeDescription IN ( 'Cached memory', 'Shared memory' ) 13 | AND [Volumes].Node.Vendor IN ( 'Synology', 'Linux' ) 14 | "@ 15 | 16 | if ( -not ( $SwisConnection ) ) 17 | { 18 | $OrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the Orion Server" 19 | $SwisCredentials = Get-Credential -Message "Enter your Orion credentials for $OrionServer" 20 | $SwisConnection = Connect-Swis -Credential $SwisCredentials -Hostname $OrionServer 21 | } 22 | 23 | $VolumesToDelete = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlQuery 24 | if ( $VolumesToDelete ) { 25 | Write-Host "Proposed volumes for deletion:" -ForegroundColor Red 26 | $VolumesToDelete | ForEach-Object { 27 | Write-Host "$( $_.VolumeDescription ) on $( $_.Node ) [$( $_.VolumePercentUsed ) % full]" -ForegroundColor Red 28 | } 29 | Write-Host "Total Count: $( $VolumesToDelete.Count )" -ForegroundColor Red 30 | 31 | $DoDelete = Read-Host -Prompt "Would you like to proceed? [Type 'delete' to confirm]" 32 | if ( $DoDelete.ToLower() -eq 'delete' ){ 33 | # This is key - if you have a bunch of URIs and you want to do the same thing on each of them, 34 | # you can pipe the contents to either Remove-SwisObject or Set-SwisObject 35 | $VolumesToDelete.Uri | Remove-SwisObject -SwisConnection $SwisConnection 36 | } else { 37 | Write-Host "'delete' response not received - No deletions were processed" -ForegroundColor Yellow 38 | } 39 | } -------------------------------------------------------------------------------- /Disable-OotbAlerts.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Script: Disable-OotbAlerts.ps1 3 | 4 | Disables all of the native Out-Of-The-Box alerts. 5 | 6 | Original Link: Disabling "Canned" Alerts [https://thwack.solarwinds.com/product-forums/the-orion-platform/f/forum/6986/disabling-canned-alerts] 7 | #> 8 | 9 | if ( -not ( $SwisConnection ) ) { 10 | $OrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the Orion Server" 11 | $SwisCredentials = Get-Credential -Message "Enter your Orion credentials for $OrionServer" 12 | $SwisConnection = Connect-Swis -Credential $SwisCredentials -Hostname $OrionServer 13 | } 14 | 15 | $Query = @" 16 | SELECT Name 17 | , Uri 18 | FROM Orion.AlertConfigurations 19 | WHERE Canned = 'TRUE' 20 | AND Enabled = 'TRUE' 21 | "@ 22 | $Alerts = Get-SwisData -SwisConnection $SwisConnection -Query $Query 23 | ForEach ( $Alert in $Alerts ) { 24 | Write-Host "Disabling OOTB Alert: $( $Alert.Name )" -ForegroundColor Red 25 | Set-SwisObject -SwisConnection $SwisConnection ` 26 | -Uri $Alert.Uri ` 27 | -Properties @{ Enabled = 'FALSE' } 28 | } 29 | -------------------------------------------------------------------------------- /ExecuteSQLQuery.ps1: -------------------------------------------------------------------------------- 1 | # The below is a SQL query, not a SWQL query 2 | # The default SolarWinds Platform user must have sufficient rights to read the database 3 | 4 | # The return formatting assumes: 5 | # * This is a SELECT query 6 | # * it only returns on dataset 7 | $SqlQuery = @" 8 | SELECT [ID] 9 | , [Guid] 10 | , [Name] 11 | , [Version] 12 | , [DisplayName] 13 | , [Description] 14 | , [Created] 15 | , [Updated] 16 | , [TemplateData] 17 | , [RequestsCount] 18 | , [MetricsCount] 19 | FROM [APIPoller_Templates] 20 | "@ 21 | 22 | # Exclude Unecessary XML details 23 | $PropertiesToExclude = @( 24 | 'Attributes', 25 | 'BaseURI', 26 | 'ChildNodes', 27 | 'FirstChild', 28 | 'HasAttributes', 29 | 'HasChildNodes', 30 | 'id', 31 | 'InnerText', 32 | 'InnerXml', 33 | 'IsEmpty', 34 | 'IsReadOnly', 35 | 'LastChild', 36 | 'LocalName', 37 | 'Name', 38 | 'NamespaceURI', 39 | 'NextSibling', 40 | 'NodeType', 41 | 'ObjectId', 42 | 'OuterXml', 43 | 'OwnerDocument', 44 | 'ParentNode', 45 | 'Prefix', 46 | 'PreviousSibling', 47 | 'PreviousText', 48 | 'rowOrder', 49 | 'SchemaInfo', 50 | 'Value' 51 | ) 52 | 53 | # Assume that we already have the Swis Connection information stored 54 | $ExecuteResponse = Invoke-SwisVerb -SwisConnection $SwisConnection -Entity 'Orion.Reporting' -Verb 'ExecuteSQL' -Arguments ( $SqlQuery, $null, $null, $false ) 55 | 56 | 57 | $DataSet = $ExecuteResponse.diffgram.DocumentElement.ExecuteSQLResults | Sort-Object -Property rowOrder | Select-Object -ExcludeProperty $PropertiesToExclude 58 | 59 | $DataSet | Format-Table -------------------------------------------------------------------------------- /Export-AtlasMapFiles.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 5 2 | #Requires -Module @{ ModuleName = 'SwisPowerShell'; ModuleVersion = '2.1.0.0' } 3 | 4 | <# 5 | Name: Export-OrionMaps.ps1 6 | Author: KMSigma [https://thwack.solarwinds.com/people/KMSigma] 7 | Purpose: Export the files needed for Network Atlas Maps - useful for when you want to re-use the graphics (Prod <--> Test <--> Dev migrations) 8 | Version History: 1.0 (2018-07-17) 9 | - Initial build for THWACKcamp 2018 10 | 1.1 (2018-10-17) 11 | - Updated with progress bars to make it more useful for a large count of files. 12 | 1.2 (2020-12-02) 13 | - Updated with some logic so that converting this to a function will be easier 14 | Requires: 15 | SolarWinds PowerShell Module (SwisPowerShell) which is documented with the SolarWinds Orion SDK [https://github.com/solarwinds/OrionSDK] 16 | If you do not have it installed, you can install it in one of two ways: 17 | 1) Install-Module -Name SwisPowerShell 18 | Installs to the default user's profile 19 | 2) Install-Module -Name SwisPowerShell -Scope AllUsers 20 | Installs to the computer's profile (available to all users) <-- this is my preferred method 21 | #> 22 | # Set up the hostname, username, and password for the source system 23 | if ( -not ( $SwisConnection ) ) 24 | { 25 | $OrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the Orion Server" 26 | $SwisCredentials = Get-Credential -Message "Enter your Orion credentials for OrionServer" 27 | $SwisConnection = Connect-Swis -Credential $SwisCredentials -Hostname $OrionServer 28 | } 29 | 30 | # This SWQL Query will export all non-deleted files 31 | $SwqlQuery = @" 32 | SELECT FileName, FileData 33 | FROM Orion.MapStudioFiles 34 | WHERE IsDeleted = False 35 | ORDER BY FileName 36 | "@ 37 | <# 38 | Notes: 39 | There is a chance that the FileName value, which is used as the export path, may be duplicated. This is an exceedingly rare and edge case I'm not taking it into account at this stage. 40 | 41 | The FileTypes stored in the table are integers and appear to be base 2. These are the ones I know and the associated extensions/types: 42 | 0 - OrionMap 43 | 2 - 'flat' images (backgrounds and things) 44 | 128 - icon images (gif or wmf) as defined on https://documentation.solarwinds.com/en/success_center/orionplatform/Content/Core-Adding-Custom-Icons-from-Graphics-Files-sw3350.htm 45 | 1024 - 'flat' image thumbnails 46 | #> 47 | 48 | 49 | # Query SWIS for the file information 50 | $SwiFiles = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlQuery 51 | 52 | # Cycle through each file and display a counter 53 | For ( $i = 0; $i -lt $SwiFiles.Count; $i++ ) 54 | { 55 | 56 | if ( $ShowProgress ) { 57 | # Progress bar showing the how we're progressing 58 | Write-Progress -Activity "Exporting Map Files" -Status "Exporting $( $SwiFiles[$i].FileName )" -PercentComplete ( ( $i / $SwiFiles.Count ) * 100 ) 59 | } 60 | 61 | # Build the output path for the file by combining the save location defined above and the file name 62 | $ExportFullPath = Join-Path -Path $SaveLocation -ChildPath $SwiFiles[$i].FileName 63 | # Check to see if the full path exists - it might not. Let's build it. 64 | if ( -not ( Test-Path -Path ( Split-Path -Path $ExportFullPath ) -ErrorAction SilentlyContinue ) ) 65 | { 66 | Write-Verbose -Message "Creating [$( ( Split-Path -Path $ExportFullPath -Parent ) )] Folder" 67 | New-Item -Path ( Split-Path -Path $ExportFullPath -Parent ) -ItemType Directory | Out-Null 68 | } 69 | 70 | # We need to see if the file already exists 71 | if ( ( Test-Path -Path $ExportFullPath -ErrorAction SilentlyContinue ) -and ( -not ( $Force ) ) ) { 72 | # The file already exists and we are not Forcing an overwrite, we skip the export 73 | Write-Warning -Message "The file $ExportFullPath already exists, skipping. To overwrite this file change `$Force to `$true" 74 | } else { 75 | # All other scenarios (we are Forcing or the file does not exist), we export 76 | $SwiFiles[$i].FileData | Set-Content -Path $ExportFullPath -Encoding Byte 77 | # I'm outputting the results of the "Get-Item" details here in preparation of moving to a function to have a return value 78 | Get-Item -Path $ExportFullPath 79 | } 80 | 81 | } 82 | 83 | if ( $ShowProgress ) { 84 | # Close the progress bar 85 | Write-Progress -Activity "Exporting Map Files" -Completed 86 | } 87 | # Cleanup - get rid of the SolarWinds-specific variables 88 | #Get-Variable -Name Swi* | Remove-Variable -------------------------------------------------------------------------------- /Export-Configs.ps1: -------------------------------------------------------------------------------- 1 | <# Export-Configs.ps1 2 | 3 | #> 4 | #Requires -Version 5 5 | #Requires -Module @{ ModuleName = 'SwisPowerShell'; ModuleVersion = '2.1.0.0' } 6 | 7 | # By default we will export the configs to a folder on the desktop - change this to your prefered folder 8 | $ExportPath = Join-Path -Path ( [System.Environment]::GetFolderPath("Desktop") ) -ChildPath "Configs" 9 | 10 | # Set up the hostname, username, and password for the source system 11 | if ( -not ( $SwisConnection ) ) 12 | { 13 | $OrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the Orion Server" 14 | $SwisCredentials = Get-Credential -Message "Enter your Orion credentials for OrionServer" 15 | $SwisConnection = Connect-Swis -Credential $SwisCredentials -Hostname $OrionServer 16 | } 17 | 18 | # This SWQL Query will collect the information we need for the configs. 19 | $SwqlQuery = @" 20 | SELECT [Configs].DownloadTime 21 | , [Configs].NodeProperties.Nodes.Caption 22 | , [Configs].ConfigType 23 | , [Configs].Baseline 24 | , [Configs].Config 25 | FROM NCM.ConfigArchive AS [Configs] 26 | WHERE IsBinary = 'False' 27 | ORDER BY DownloadTime ASC 28 | "@ 29 | 30 | # Build the initial export folder if necessary 31 | if ( -not ( Test-Path -Path $ExportPath -ErrorAction SilentlyContinue ) ) { 32 | Write-Host "Creating folder at [$ExportPath]" -ForegroundColor Yellow 33 | New-Item -ItemType Directory -Path $ExportPath | Out-Null 34 | } 35 | 36 | # Note: The config files themselves can be tens of thousands of characters, so this query may take a little to run 37 | $ConfigList = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlQuery 38 | 39 | 40 | if ( $ConfigList ) { 41 | # The following calculated properties are added to our object to allow for consistent naming and folder structure. 42 | # It's not strictly necessary in this way - it's all on preference. 43 | # This example is for: [Current User's Desktop Folder]\Configs\[Caption]\[Caption]_[DateString]_[ConfigType](_Baseline).txt 44 | # The word "Baseline" will be added to the end if it's tagged as a baseline, otherwise, it's omitted 45 | 46 | 47 | # Manipulate the date to be a better match for sorting 48 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "DateString" -Value { $this.DownloadTime.ToString("yyyy-MM-dd") } -Force 49 | # Calculate the directory name for the of the export file 50 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "DirectoryName" -Value { Join-Path -Path $ExportPath -ChildPath ( $this.Caption ) } -Force 51 | # Calculate the file name 52 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "FileName" -Value { "$( $this.Caption )_$( $this.DateString )_$( $this.ConfigType )$( if ( $this.Baseline ) { "_Baseline" } ).txt" } -Force 53 | # Figure out the full file name for export 54 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "FullName" -Value { Join-Path -Path $this.DirectoryName -ChildPath $this.FileName } -Force 55 | 56 | # Not to cycle through each one 57 | ForEach ( $Config in $ConfigList ) { 58 | # We need to see if the folder already exists, and if not create it 59 | if ( -not ( Test-Path -Path $Config.DirectoryName -ErrorAction SilentlyContinue ) ) { 60 | Write-Host "Creating folder at [$( $Config.DirectoryName )]" -ForegroundColor Yellow 61 | New-Item -ItemType Directory -Path $Config.DirectoryName | Out-Null 62 | } 63 | Write-Host "`tWriting out $( $Config.FileName )" 64 | $Config.Config | Out-File -FilePath $Config.FullName -Encoding ASCII -Force 65 | } 66 | } 67 | else { 68 | Write-Host "No Configurations Found" -ForegroundColor Red 69 | } 70 | -------------------------------------------------------------------------------- /Export-ConfigsSQL.ps1: -------------------------------------------------------------------------------- 1 | <# Export-Configs.ps1 2 | 3 | #> 4 | #Requires -Version 5 5 | #Requires -Module @{ ModuleName = 'SqlServer'; ModuleVersion = '21.0.0.0' } 6 | 7 | # By default we will export the configs to a folder on the desktop - change this to your prefered folder 8 | $ExportPath = Join-Path -Path ( [System.Environment]::GetFolderPath("Desktop") ) -ChildPath "Configs" 9 | 10 | # Set up the hostname, username, and password for the source SQL server 11 | $SqlServerInstance = "SqlServer.Domain.Local" # or "SqlServer.Domain.Local\InstanceName" 12 | $SqlDatabase = "SolarWindsOrion" 13 | $SqlUsername = "SolarWindsOrionDatabaseUser" 14 | $SqlPassword = "ThisIsNotMyPassword" 15 | 16 | 17 | $SqlQuery = @" 18 | SELECT [Nodes].[NodeCaption] AS [Caption] 19 | , [Configs].[DownloadTime] 20 | , [Configs].[ConfigType] 21 | , [Configs].[Config] 22 | , [Configs].[Baseline] 23 | FROM [NCM_ConfigArchive] AS [Configs] 24 | INNER JOIN NCM_Nodes AS [Nodes] 25 | ON [Configs].NodeID = [Nodes].NodeID 26 | WHERE [Configs].IsBinary = 0 27 | ORDER BY DownloadTime ASC 28 | "@ 29 | 30 | # Build the initial export folder if necessary 31 | if ( -not ( Test-Path -Path $ExportPath -ErrorAction SilentlyContinue ) ) { 32 | Write-Host "Creating folder at [$ExportPath]" -ForegroundColor Yellow 33 | New-Item -ItemType Directory -Path $ExportPath | Out-Null 34 | } 35 | 36 | $ConfigList = Invoke-Sqlcmd -Query $SqlQuery -ServerInstance $SqlServerInstance -Database $SqlDatabase -Username $SqlUsername -Password $SqlPassword 37 | 38 | if ( $ConfigList ) { 39 | # The following calculated properties are added to our object to allow for consistent naming and folder structure. 40 | # It's not strictly necessary in this way - it's all on preference. 41 | # This example is for: [Current User's Desktop Folder]\Configs\[Caption]\[Caption]_[DateString]_[ConfigType](_Baseline).txt 42 | # The word "Baseline" will be added to the end if it's tagged as a baseline, otherwise, it's omitted 43 | 44 | 45 | # Manipulate the date to be a better match for sorting 46 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "DateString" -Value { $this.DownloadTime.ToString("yyyy-MM-dd") } -Force 47 | # Calculate the directory name for the of the export file 48 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "DirectoryName" -Value { Join-Path -Path $ExportPath -ChildPath ( $this.Caption ) } -Force 49 | # Calculate the file name 50 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "FileName" -Value { "$( $this.Caption )_$( $this.DateString )_$( $this.ConfigType )$( if ( $this.Baseline ) { "_Baseline" } ).txt" } -Force 51 | # Figure out the full file name for export 52 | $ConfigList | Add-Member -MemberType ScriptProperty -Name "FullName" -Value { Join-Path -Path $this.DirectoryName -ChildPath $this.FileName } -Force 53 | 54 | # Not to cycle through each one 55 | ForEach ( $Config in $ConfigList ) { 56 | # We need to see if the folder already exists, and if not create it 57 | if ( -not ( Test-Path -Path $Config.DirectoryName -ErrorAction SilentlyContinue ) ) { 58 | Write-Host "Creating folder at [$( $Config.DirectoryName )]" -ForegroundColor Yellow 59 | New-Item -ItemType Directory -Path $Config.DirectoryName | Out-Null 60 | } 61 | Write-Host "`tWriting out $( $Config.FileName )" 62 | $Config.Config | Out-File -FilePath $Config.FullName -Encoding ASCII -Force 63 | } 64 | } 65 | else { 66 | Write-Host "No Configurations Found" -ForegroundColor Red 67 | } 68 | -------------------------------------------------------------------------------- /Export-DeviceTemplates.ps1: -------------------------------------------------------------------------------- 1 | <###################################################################### 2 | Name: Export-DeviceTemplates.ps1 3 | Author: Kevin M. Sparenberg 4 | ----------------------------------------------------------------------- 5 | Purpose: Export all non-default NCM Device Templates from Orion Server 6 | 7 | Version History: 8 | 1.0 - 01AUG2017 - Initial Build 9 | 10 | Notes: 11 | Change the elements in the Declare Variables region to your own needs 12 | 13 | If you want to export ALL templates, then remove the WHERE clause 14 | in the SWQL Query. 15 | ######################################################################> 16 | 17 | #region Declare Variables 18 | # Export Path in Local File System 19 | $ExportPath = ".\DeviceTemplateExports\" 20 | 21 | # Test Export Path & Create folder if doesn't exist 22 | if ( -not ( Test-Path -Path $ExportPath -ErrorAction SilentlyContinue ) ) { 23 | Write-Warning -Message "Creating Folder at $ExportPath" 24 | New-Item -Path $ExportPath -ItemType Directory | Out-Null 25 | } 26 | 27 | 28 | # Orion Username & Password & Host (Name or IP) 29 | $SwisHost = "kmsorion01v.kmsigma.local" 30 | 31 | # SWQL Query for Templates 32 | # Only includes non-default (last line) 33 | # To include all templates, remove the WHERE clause 34 | $Swql = @" 35 | SELECT TemplateName 36 | , SystemOID 37 | , TemplateXml 38 | , SystemDescriptionRegex 39 | , CASE AutoDetectType 40 | WHEN 0 THEN 'BySystemOid' 41 | WHEN 1 THEN 'BySystemDescription' 42 | END AS [AutoDetectType] 43 | FROM Cli.DeviceTemplates 44 | WHERE IsDefault <> 'True' 45 | "@ 46 | #endregion Declare Variables 47 | 48 | #region Connect to SWIS 49 | if ( -not ( $SwisCred ) ) { 50 | $SwisCred = Get-Credential -Message "Enter your Orion Credentails for '$SwisHost'" 51 | } 52 | 53 | $SwisConnection = Connect-Swis -Hostname $SwisHost -Credential $SwisCred 54 | #endregion Connect to SWIS 55 | 56 | # Query for all "non-default" Templates 57 | $DeviceTemplates = Get-SwisData -SwisConnection $SwisConnection -Query $Swql 58 | 59 | # Add 'filename' member 60 | $DeviceTemplates | Add-Member -MemberType ScriptProperty -Name "Filename" -Value { $this.TemplateName + '-' + $this.SystemOID + '.ConfigMgmt-Commands' } 61 | 62 | #region Export Templates to File System 63 | ForEach ( $DeviceTemplate in $DeviceTemplates ) { 64 | try { 65 | $TemplateBodyXML = [xml]( $DeviceTemplate.TemplateXml ) 66 | $TemplateBodyXML.'Configuration-Management'.Device = $DeviceTemplate.TemplateName 67 | if ( -not ( $TemplateBodyXML.'Configuration-Management'.SystemOID ) ) { 68 | $TemplateBodyXML.'Configuration-Management'.SetAttribute("SystemOID", $DeviceTemplate.SystemOID) 69 | } 70 | else { 71 | $TemplateBodyXML.'Configuration-Management'.SystemOID = $DeviceTemplate.SystemOID 72 | } 73 | if ( -not ( $TemplateBodyXML.'Configuration-Management'.SystemDescriptionRegex ) ) { 74 | $TemplateBodyXML.'Configuration-Management'.SetAttribute("SystemDescriptionRegex", $DeviceTemplate.SystemDescriptionRegex) 75 | } 76 | else { 77 | $TemplateBodyXML.'Configuration-Management'.SystemDescriptionRegex = $DeviceTemplate.SystemDescriptionRegex 78 | } 79 | $TemplateBodyXML.'Configuration-Management'.SetAttribute("AutoDetectType", $DeviceTemplate.AutoDetectType) 80 | $TemplateBodyXML.Save( ( Join-Path -Path $ExportPath -ChildPath $DeviceTemplate.FileName ) ) 81 | } 82 | catch { 83 | Write-Error -Message "Ran into an error processing $( $DeviceTemplate.TemplateName )." 84 | } 85 | } 86 | #endregion Export Templates to File System -------------------------------------------------------------------------------- /Export-OrionAlerts.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Export all the alerts from Orion. Overwrites existing XML files by default. 3 | 4 | Tested on Orion Platform 2020.2.5 5 | #> 6 | 7 | $Swql = @" 8 | SELECT AlertID 9 | , Name 10 | FROM Orion.AlertConfigurations 11 | ORDER BY AlertID 12 | "@ 13 | 14 | if ( -not ( $SwisConnection ) ) { 15 | $SwisHost = Read-Host -Prompt "Provide the IP or FQDN of your Orion server" 16 | 17 | if ( -not ( $SwisCreds ) ) { 18 | $SwisCreds = Get-Credential -Message "Enter your Orion credentials for $SwisHost" 19 | } 20 | 21 | if ( $SwisHost -and $SwisCreds ) { 22 | $SwisConnection = Connect-Swis -Hostname $SwisHost -Credential $SwisCreds 23 | } 24 | } 25 | $ExportPath = ".\AlertExports\" 26 | 27 | $SwisConnection = Connect-Swis -Hostname $SwisHost -Credential $SwisCreds 28 | $AlertList = Get-SwisData -SwisConnection $SwisConnection -Query $Swql 29 | 30 | # Check to see if the Export Path exists. If not, then create it. 31 | if ( -not ( Test-Path -Path $ExportPath -ErrorAction SilentlyContinue ) ) { 32 | $ExportRoot = New-Item -ItemType Directory -Path $ExportPath 33 | } 34 | else { 35 | $ExportRoot = Get-Item -Path $ExportPath 36 | } 37 | 38 | For ( $i = 0; $i -lt $AlertList.Count; $i++ ) { 39 | Write-Progress -Activity "Export Alerts from $SwisHost" -CurrentOperation "Processing $( $AlertList[$i].Name )" -PercentComplete ( ( $i / $AlertList.Count ) * 100 ) 40 | # Arguments for the Orion.AlertConfigurations / Export verb: 41 | # [int]AlertId (mandatory) 42 | # [bool]StripSensitiveData (optional) 43 | # [string]ProtectionPassword (optional) 44 | $ExportArguments = $AlertList[$i].AlertID #, $false, "strongPassword" 45 | 46 | $FileName = [System.Web.HttpUtility]::UrlEncode($AlertList[$i].Name) + '.xml' 47 | 48 | # Build the full path name to the file (needed for the export) 49 | $FilePath = Join-Path -Path $ExportRoot -ChildPath $FileName 50 | 51 | try { 52 | # Pull the alert definition and then just select the actual XML 53 | $Export = Invoke-SwisVerb -SwisConnection $SwisConnection -EntityName "Orion.AlertConfigurations" -Verb "Export" -Arguments $ExportArguments -ErrorAction SilentlyContinue 54 | if ( $Export ) { 55 | $RawXml = $Export.InnerText 56 | ( [xml]$RawXml ).Save($FilePath) 57 | } 58 | } 59 | catch { 60 | Write-Error -Message "Processing Error on $( $AlertList[$i].Name ) / [$( $AlertList[$i].AlertId )]" 61 | } 62 | } 63 | Write-Progress -Activity "Export Alerts from $SwisHost" -Completed 64 | -------------------------------------------------------------------------------- /Export-ProductKeys.ps1: -------------------------------------------------------------------------------- 1 | <################################################## 2 | Export-ProductKeys.ps1 3 | 4 | This script exports the product keys from your SolarWinds Orion instance. 5 | It displays there mere and exports them to a CSV on the current user's desktop 6 | 7 | This is useful if you plan on migrating to new hardware. 8 | ---Tested with Core 2020.2.1 HF1--- 9 | ##################################################> 10 | 11 | $SwisHost = Read-Host -Prompt "Provide the IP or FQDN of your Orion server" 12 | 13 | if ( -not ( $SwisCreds ) ) 14 | { 15 | $SwisCreds = Get-Credential -Message "Enter your Orion credentials for $SwisHost" 16 | } 17 | 18 | if ( $SwisHost -and $SwisCreds ) 19 | { 20 | $SwisConnection = Connect-Swis -Hostname $SwisHost -Credential $SwisCreds 21 | } 22 | 23 | # Get the License information for your Orion Servers" 24 | 25 | $SwqlOrionServerLicenses = @" 26 | SELECT [Licenses].OrionServer.Hostname 27 | , [Licenses].OrionServer.ServerType 28 | , CASE [Licenses].ProductName 29 | WHEN 'SAM' THEN 'Server & Application Monitor' 30 | WHEN 'WPM' THEN 'Web Performance Monitor' 31 | WHEN 'VNQM' THEN 'Voice & Network Quality Monitor' 32 | WHEN 'VM' THEN 'Virtualization Manager' 33 | WHEN 'UDT' THEN 'User Device Tracker' 34 | WHEN 'STM' THEN 'Storage Resource Monitor' 35 | WHEN 'SCM' THEN 'Server Configuration Monitor' 36 | WHEN 'NCM' THEN 'Network Configuration Monitor' 37 | WHEN 'IPAM' THEN 'IP Address Manager' 38 | WHEN 'NPM' THEN 'Network Performance Monitor' 39 | WHEN 'Orion NetFlow Traffic Analyzer' THEN 'NetFlow Traffic Analyzer' 40 | WHEN 'LM' THEN 'Log Analyzer' 41 | WHEN 'WebToolset' THEN 'Enterprise Toolset' 42 | END AS [Product] 43 | , [Licenses].LicenseKey 44 | FROM Orion.Licensing.LicenseAssignments AS [Licenses] 45 | ORDER BY [Product] 46 | 47 | "@ 48 | 49 | if ( $SwisConnection ) 50 | { 51 | $LicenseData = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlOrionServerLicenses 52 | $LicenseData 53 | $LicenseData | Export-Csv -Path ( Join-Path -Path ( [System.Environment]::GetFolderPath("Desktop") ) -ChildPath "OrionLicenses.csv" ) -Force -NoTypeInformation 54 | } 55 | else 56 | { 57 | Write-Error -Message "There's a problem with your connection to the SolarWinds Information Service" 58 | } 59 | 60 | Get-Variable -Name Swis* | Remove-Variable -ErrorAction SilentlyContinue -------------------------------------------------------------------------------- /Export-SwisCp.ps1: -------------------------------------------------------------------------------- 1 | # Export-SwisCp.ps1 2 | #Requires -Module @{ ModuleName = 'SwisPowerShell'; ModuleVersion = '3.0.0' } 3 | 4 | 5 | <# 6 | Notes: 7 | 8 | When exporting from the web console, there are the following nuances: 9 | Each CP has an additional SHA1() 10 | 11 | Node Custom Properties: 12 | Includes Caption and IP_Address 13 | 14 | Alert Custom Properties: 15 | Includes Name 16 | 17 | 18 | 19 | #> 20 | #region ConvertTo-Hash function 21 | <# 22 | .Synopsis 23 | Convert a string to a hash value 24 | .DESCRIPTION 25 | The `ConvertTo-Hash` does a thing 26 | .EXAMPLE 27 | Convert a single string using SHA256 (the default) 28 | 29 | ConvertTo-Hash -String "Hello world" -Algorithm sha256 30 | 31 | Algorithm Hash String 32 | --------- ---- ------ 33 | SHA256 64EC88CA00B268E5BA1A35678A1B5316D212F4F366B2477232534A8AECA37F3C Hello world 34 | .EXAMPLE 35 | Convert a single string using MD5 and return only the hash 36 | 37 | ConvertTo-Hash -String "foo bar" -Algorithm MD5 -OnlyHash 38 | 39 | 327B6F07435811239BC47E1544353273 40 | .EXAMPLE 41 | Convert multiple strings using SHA1 42 | 43 | ConvertTo-Hash -string "foo", "bar", "hello", "world" -Algorithm SHA1 44 | 45 | Algorithm Hash String 46 | --------- ---- ------ 47 | SHA1 0BEEC7B5EA3F0FDBC95D0DD47F3C5BC275DA8A33 foo 48 | SHA1 62CDB7020FF920E5AA642C3D4066950DD1F01F4D bar 49 | SHA1 AAF4C61DDCC5E8A2DABEDE0F3B482CD9AEA9434D hello 50 | SHA1 7C211433F02071597741E6FF5A8EA34789ABBF43 world 51 | .INPUTS 52 | string or strings 53 | .OUTPUTS 54 | PowerShell object or string with the hash 55 | .NOTES 56 | Completely and shamelessly stolen from Microsoft's example with only a few variable name tweaks 57 | URL: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash?view=powershell-7.2&WT.mc_id=ps-gethelp#example-4-compute-the-hash-of-a-string 58 | #> 59 | function ConvertTo-Hash 60 | { 61 | [CmdletBinding(DefaultParameterSetName='Default', 62 | SupportsShouldProcess=$false, 63 | PositionalBinding=$true, 64 | HelpUri = 'https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash?view=powershell-7.2&WT.mc_id=ps-gethelp#example-4-compute-the-hash-of-a-string')] 65 | [Alias()] 66 | [OutputType([String])] 67 | Param 68 | ( 69 | # The string (or strings) to encode 70 | [Parameter(Mandatory=$true, 71 | ValueFromPipeline=$true, 72 | ValueFromPipelineByPropertyName=$true, 73 | ValueFromRemainingArguments=$false, 74 | ParameterSetName='Default', 75 | Position=0)] 76 | [ValidateNotNullOrEmpty()] 77 | [Alias("p1")] 78 | [string[]]$String, 79 | 80 | # Specifies the cryptographic hash function to use for computing the hash value of the contents of the specified file or stream. A cryptographic hash function has the property that it is infeasible to find two different files with the same hash value. Hash functions are commonly used with digital signatures and for data integrity. The acceptable values for this parameter are: 81 | # 82 | # SHA1 83 | # SHA256 84 | # SHA384 85 | # SHA512 86 | # MD5 87 | # 88 | # If no value is specified, or if the parameter is omitted, the default value is SHA256. 89 | # 90 | # For security reasons, MD5 and SHA1, which are no longer considered secure, should only be used for simple change validation, and should not be used to generate hash values for files that require protection from attack or tampering. 91 | [Parameter(ParameterSetName='Default')] 92 | [AllowNull()] 93 | [ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512', 'MD5')] 94 | [string]$Algorithm = 'SHA256', 95 | 96 | # Return only the hash of the informaiton instead of the full object 97 | [Parameter(ParameterSetName='Default')] 98 | [switch]$OnlyHash 99 | 100 | ) 101 | 102 | Begin 103 | { 104 | # Nothing to see here... 105 | } 106 | Process 107 | { 108 | ForEach ( $s in $String ) { 109 | $StringStream = [System.IO.MemoryStream]::new() 110 | $StreamWriter = [System.IO.StreamWriter]::new($stringStream) 111 | $StreamWriter.write($s) 112 | $StreamWriter.Flush() 113 | $StringStream.Position = 0 114 | if ( $OnlyHash ) { 115 | Get-FileHash -InputStream $StringStream -Algorithm $Algorithm | Select-Object -ExpandProperty Hash 116 | } 117 | else { 118 | Get-FileHash -InputStream $StringStream -Algorithm $Algorithm | Select-Object -Property Algorithm, Hash, @{ Name = 'String'; Expression = { $s } } 119 | } 120 | 121 | } 122 | 123 | } 124 | End 125 | { 126 | # Nothing to see here either... 127 | } 128 | } 129 | #endregion ConvertTo-Hash function 130 | 131 | 132 | 133 | $ExecutionStartTime = ( Get-Date -Format 's' ).Replace(":", "-") 134 | $ExecutionStartTime = ( Get-Date ).ToString("yyyy-MM-dd") 135 | 136 | <# 137 | 138 | Yet to be done: 139 | Entities of type: 140 | - Orion.SRM.ProviderCustomProperties 141 | - Orion.SRM.StorageArrayCustomProperties 142 | - Orion.SRM.StorageControllerCustomProperties 143 | - Orion.SRM.StorageControllerPortCustomProperties 144 | - Orion.SRM.VolumeCustomProperties 145 | - Orion.VIM.ClustersCustomProperties 146 | - Orion.VIM.DataCentersCustomProperties 147 | - Orion.VIM.DatastoresCustomProperties 148 | - Orion.VIM.HostsCustomProperties 149 | - Orion.VIM.VirtualMachinesCustomProperties 150 | 151 | #> 152 | 153 | #region Build the SolarWinds Information Service connection 154 | # Build the connection to SolarWinds Orion 155 | # This example prompts for the server name/IP and then asks for the username/password combo 156 | if ( -not $SwisConnection ) { 157 | $SwisHostname = Read-Host -Prompt "Please enter the DNS or IP of your Orion Server" 158 | $SwisCredential = Get-Credential -Message "Provide the username/password for '$SwisHostname'" 159 | 160 | $SwisConnection = Connect-Swis -Hostname $SwisHostname -Credential $SwisCredential 161 | # Once we have the connection, we don't need the credentials, so remove them. 162 | Remove-Variable -Name SwisHostname, SwisCredential -ErrorAction SilentlyContinue 163 | } 164 | # Certificate authentication assumes you are running on the local Orion server, if not, use a different authentication method 165 | #$SwisConnection = Connect-Swis -Hostname 'kmsorion01v.kmsigma.local' -Certificate 166 | #endregion Build the SolarWinds Information Service connection 167 | 168 | 169 | # Select the location where we want to store the exported files 170 | $ExportPath = ".\CustomPropertyExports" 171 | #region Quick Check to see if the folder exists, and if not, create it 172 | if ( -not ( Test-Path -Path $ExportPath -ErrorAction SilentlyContinue ) ) { 173 | New-Item -Path $ExportPath -ItemType Directory | Out-Null 174 | } 175 | #endregion Quick Check to see if the folder exists, and if not, create it 176 | 177 | 178 | 179 | 180 | # Define a global 'alias' for the Custom Property Lookups 181 | $CpAlias = '[CP]' 182 | 183 | # This will retrieve the list of ALL the custom properties, regardless of where they are used 184 | $SwqlCpList = @" 185 | SELECT $( $CpAlias ).Table 186 | , $( $CpAlias ).Field 187 | , $( $CpAlias ).DataType 188 | , $( $CpAlias ).MaxLength 189 | , $( $CpAlias ).Description 190 | , $( $CpAlias ).TargetEntity 191 | , $( $CpAlias ).Mandatory 192 | FROM Orion.CustomProperty AS $( $CpAlias ) 193 | ORDER BY $( $CpAlias ).TargetEntity, $( $CpAlias ).Field 194 | "@ 195 | 196 | $SwisHost = $SwisConnection.ChannelFactory.Endpoint.address.Uri.Host 197 | 198 | # Run the query to get the list of all Custom Properties 199 | $ListOfCps = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlCpList 200 | 201 | # Export all of the CP's to have a record 202 | $ListOfCps | Export-Csv -Path ( Join-Path -Path $ExportPath -ChildPath "$( $SwisHost )_CustomProperties.csv" ) -Force -Confirm:$false -NoTypeInformation 203 | 204 | # Let's get the list of distinct target entities and just store they as an array of strings 205 | $TargetEntities = $ListOfCps | Select-Object -Property TargetEntity -Unique | Select-Object -ExpandProperty TargetEntity | Sort-Object 206 | 207 | 208 | #region Identifying Details/Filters/Sorting 209 | <# 210 | Custom Properties are defined by their resultant Target Entity (Nodes, Interfaces, Reports, etc.) 211 | Since each table in the SDK has slighly different formatting when it comes to Navigation Properties 212 | We need to build some 'default' fields to pull, as well as filtering and sorting 213 | 214 | This is done in a series of hastables, so we can 'reference' the specific values by the Target Entity 215 | This only needs to be done once before we start building the queries 216 | #> 217 | 218 | 219 | 220 | # To do this work we'll need some 'default' fields so we can identify what we're looking at 221 | $BaseCpFields = @{ 222 | "IPAM.GroupsCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).GroupNode.FriendlyName, $( $CpAlias ).GroupNode.Address, $( $CpAlias ).GroupNode.CIDR, $( $CpAlias ).GroupNode.GroupTypeText" 223 | "IPAM.NodesCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).IPNode.SysName, $( $CpAlias ).IPNode.DnsBackward, $( $CpAlias ).IPNode.IPAddress, $( $CpAlias ).IPNode.MAC" 224 | "Orion.AlertConfigurationsCustomProperties" = "$( $CpAlias ).Alert.Uri + '/CustomProperties' AS [Uri], $( $CpAlias ).Alert.Name" 225 | "Orion.APM.ApplicationCustomProperties" = "$( $CpAlias ).Application.Uri + '/CustomProperties' AS [Uri], $( $CpAlias ).Application.Node.Caption, $( $CpAlias ).Application.Name" 226 | "Orion.GroupCustomProperties" = "$( $CpAlias ).[Group].Uri + '/CustomProperties' AS [Uri], $( $CpAlias ).[Group].Name" 227 | "Orion.NodesCustomProperties" = "$( $CpAlias ).Node.Caption, $( $CpAlias ).Uri, $( $CpAlias ).Node.IPAddress" 228 | "Orion.NPM.InterfacesCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).Interface.FullName" 229 | "Orion.ReportsCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).Report.Title" 230 | "Orion.SEUM.RecordingCustomProperties" = "$( $CpAlias ).Recording.Uri + '/CustomProperties' AS [Uri], $( $CpAlias ).Recording.DisplayName, $( $CpAlias ).Recording.Description" 231 | "Orion.SEUM.TransactionCustomProperties" = "$( $CpAlias ).Transaction.Uri + '/CustomProperties' AS [Uri], $( $CpAlias ).Transaction.DisplayName, $( $CpAlias ).Transaction.Description" 232 | # Assumptions Made About URIs 233 | "Orion.SRM.FileShareCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).FileShares.Name, $( $CpAlias ).FileShares.UserCaption, $( $CpAlias ).FileShares.Caption, $( $CpAlias ).FileShares.Description" 234 | "Orion.SRM.LUNCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).LUNs.Name, $( $CpAlias ).LUNs.UserCaption, $( $CpAlias ).LUNs.Caption, $( $CpAlias ).LUNs.Description" 235 | "Orion.SRM.PoolCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).Pools.Name, $( $CpAlias ).Pools.UserCaption, $( $CpAlias ).Pools.Caption, $( $CpAlias ).Pools.Description" 236 | "Orion.SRM.ProviderCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).Providers.Name, $( $CpAlias ).Providers.UserCaption, $( $CpAlias ).Providers.Caption, $( $CpAlias ).Providers.Description" 237 | "Orion.SRM.StorageArrayCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).StorageArrays.Name, $( $CpAlias ).StorageArrays.UserCaption, $( $CpAlias ).StorageArrays.Caption, $( $CpAlias ).StorageArrays.Description" 238 | "Orion.SRM.VolumeCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).Volumes.Name, $( $CpAlias ).Volumes.UserCaption, $( $CpAlias ).Volumes.Caption, $( $CpAlias ).Volumes.Description" 239 | "Orion.VIM.DataCentersCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).DataCenter.Name, $( $CpAlias ).DataCenter.Description" 240 | # Yet to do 241 | # Orion.VIM.DatastoresCustomProperties 242 | # Orion.VIM.HostsCustomProperties 243 | # Orion.VIM.VirtualMachinesCustomProperties 244 | "Orion.VolumesCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).Volume.Node.Caption AS [Node], $( $CpAlias ).Volume.Description, $( $CpAlias ).Volume.VolumeType" 245 | 246 | # Known Bad Linkage 247 | #"Orion.SRM.StorageControllerCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).StorageControllers.Name, $( $CpAlias ).StorageControllers.UserCaption, $( $CpAlias ).StorageControllers.Caption, $( $CpAlias ).StorageControllers.Description" 248 | #"Orion.SRM.StorageControllerPortCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).StorageControllerPorts.Name, $( $CpAlias ).StorageControllerPorts.DisplayName, $( $CpAlias ).StorageControllerPorts.Description" 249 | #"Orion.VIM.ClustersCustomProperties" = "$( $CpAlias ).Uri, $( $CpAlias ).Cluster.Name, $( $CpAlias ).Cluster.Caption, $( $CpAlias ).Cluster.Description" 250 | } 251 | 252 | # Some of the queries would benefit from filtering off some information 253 | $WhereClauses = @{ 254 | "IPAM.GroupsCustomProperties" = "WHERE $( $CpAlias ).GroupNode.CIDR <> 0" 255 | "Orion.GroupCustomProperties" = "WHERE $( $CpAlias ).[Group].Owner <> 'Maps'" 256 | } 257 | 258 | # This sorting is optional, but incredibly helpful 259 | $OrderByClauses = @{ 260 | "IPAM.GroupsCustomProperties" = "ORDER BY $( $CpAlias ).GroupNode.ParentID, $( $CpAlias ).GroupNode.GroupType" 261 | "IPAM.NodesCustomProperties" = "ORDER BY $( $CpAlias ).IpNode.IPAddressN" 262 | "Orion.AlertConfigurationsCustomProperties" = "ORDER BY $( $CpAlias ).Alert.Name" 263 | "Orion.APM.ApplicationCustomProperties" = "ORDER BY $( $CpAlias ).Application.Name, $( $CpAlias ).Application.Node.Caption" 264 | "Orion.GroupCustomProperties" = "ORDER BY $( $CpAlias ).[Group].Name" 265 | "Orion.NodesCustomProperties" = "ORDER BY $( $CpAlias ).Node.Caption, $( $CpAlias ).Node.IPAddress" 266 | "Orion.NPM.InterfacesCustomProperties" = "ORDER BY $( $CpAlias ).Interface.FullName" 267 | "Orion.ReportsCustomProperties" = "ORDER BY $( $CpAlias ).Report.Title" 268 | "Orion.SEUM.RecordingCustomProperties" = "ORDER BY $( $CpAlias ).Recording.DisplayName" 269 | "Orion.SEUM.TransactionCustomProperties" = "ORDER BY $( $CpAlias ).Transaction.DisplayName" 270 | "Orion.SRM.FileShareCustomProperties" = "ORDER BY $( $CpAlias ).FileShares.DisplayName" 271 | "Orion.SRM.LUNCustomProperties" = "ORDER BY $( $CpAlias ).LUNs.DisplayName" 272 | } 273 | #endregion Identifying Details/Filters/Sorting 274 | 275 | # Cycle through each distinct Target Entity type and build a query. 276 | ForEach ( $TargetEntity in $TargetEntities ) { 277 | # Get the list of fields from each entity type 278 | $Fields = $ListOfCps | Where-Object { $_.TargetEntity -eq $TargetEntity } 279 | # Now the $Fields variable contains the names of the fields for the current Target Entity type 280 | # But, I want to use the Alias, so I'll need to do some clever convertion and then store them as strings (so I can use the -join operator) 281 | # I also added an alias for CP_(FieldName) so in the exported CSV, it's (hopefully) obvious which fields are custom properties 282 | $FieldsWithAlias = $Fields | Select-Object -Property @{ Name = 'Field'; Expression = { "$( $CpAlias ).$( $_.Field ) AS CP_$( $_.Field )" } } | Select-Object -ExpandProperty Field 283 | 284 | # The query takes the form: 285 | # SELECT (Base Fields), (Fields From Custom Properties), (Custom Property's URI) FROM (Target Entity) (WHERE/filter clauses) (ORDER BY/sorting clauses) 286 | if ( $BaseCpFields[$TargetEntity] ) { 287 | $CpQuery = "SELECT $( $BaseCpFields[$TargetEntity] ), $( $FieldsWithAlias -join ", " ) FROM $TargetEntity AS $CpAlias $( $WhereClauses[$TargetEntity] ) $( $OrderByClauses[$TargetEntity] )" 288 | Write-Verbose -Message "Execution: $CpQuery" 289 | $Results = Get-SwisData -SwisConnection $SwisConnection -Query $CpQuery 290 | if ( $Results ) { 291 | Write-Host "Exporting $( $Results.Count) record(s) from $TargetEntity to '$( $ExportPath )\$( $TargetEntity ).csv" 292 | $Results | Export-Csv -Path ( Join-Path -Path $ExportPath -ChildPath "$( $TargetEntity ).csv" ) -Force -Confirm:$false -NoTypeInformation 293 | } 294 | else { 295 | Write-Warning -Message "No entries found for Custom Properties for '$TargetEntity'" 296 | "No entries found for Custom Properties for '$TargetEntity'" | Out-File -FilePath ( Join-Path -Path $ExportPath -ChildPath "Error_$( $SwisHost )_$( $ExecutionStartTime ).log" ) -Append 297 | "SWQL EXECUTING: $CpQuery" | Out-File -FilePath ( Join-Path -Path $ExportPath -ChildPath "Error$( $SwisHost )__$( $ExecutionStartTime ).log" ) -Append 298 | } 299 | } 300 | else { 301 | Write-Error -Message "No 'Default' Custom Properties are defined for '$TargetEntity'" -RecommendedAction "Update the script to fix it" 302 | "No 'Default' Custom Properties are defined for '$TargetEntity'" | Out-File -FilePath ( Join-Path -Path $ExportPath -ChildPath "Error_$( $SwisHost )_$( $ExecutionStartTime ).log" ) -Append 303 | } 304 | } -------------------------------------------------------------------------------- /Get-OrionServerInformation.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Script: Get-OrionServerDetails.sql 3 | Purpose: Retrieve versions from the SolarWinds Orion Database 4 | 5 | Scripts are not supported under any SolarWinds support program or service. 6 | Scripts are provided AS IS without warranty of any kind. SolarWinds further 7 | disclaims all warranties including, without limitation, any implied warranties 8 | of merchantability or of fitness for a particular purpose. The risk arising 9 | out of the use or performance of the scripts and documentation stays with you. 10 | In no event shall SolarWinds or anyone else involved in the creation, 11 | production, or delivery of the scripts be liable for any damages whatsoever 12 | (including, without limitation, damages for loss of business profits, business 13 | interruption, loss of business information, or other pecuniary loss) arising 14 | out of the use of or inability to use the scripts or documentation. 15 | */ 16 | 17 | -- 'SolarWindsOrion' is the default name of the Orion database. 18 | -- If you used a different name, replace it below 19 | USE SolarWindsOrion; 20 | 21 | SELECT [Hostname], 22 | [ServerType], 23 | CASE 24 | WHEN ( 25 | EXISTS ( 26 | SELECT * 27 | FROM Licensing_LicenseAssignments 28 | WHERE [ProductName] = 'VM' 29 | ) 30 | AND ([Acronym] = 'VMAN') 31 | ) 32 | THEN 'Virtualization Manager' 33 | WHEN ( 34 | NOT EXISTS ( 35 | SELECT * 36 | FROM Licensing_LicenseAssignments 37 | WHERE [ProductName] = 'VM' 38 | ) 39 | AND ([Acronym] = 'VMAN') 40 | ) 41 | THEN 'Virtual Infrastructure Monitor' 42 | ELSE [Product] 43 | END AS [Product], 44 | [Acronym], 45 | CASE 46 | WHEN HotFix IS NULL 47 | THEN [ReleaseVersion] 48 | ELSE [ReleaseVersion] + ' HF' + [HotFix] 49 | END AS [Version] 50 | FROM [OrionServers] 51 | CROSS APPLY OPENJSON([Details]) WITH ( 52 | [Product] VARCHAR(50) '$.Name', 53 | [Acronym] VARCHAR(5) '$.ShortName', 54 | [ReleaseVersion] VARCHAR(25) '$.Version', 55 | [Hotfix] VARCHAR(5) '$.HotfixVersionNumber' 56 | ) AS VersionInfo 57 | -- Remove 'features' that are erroneously listed as a 'product' 58 | WHERE [Product] NOT IN ('Cloud Monitoring', 'Quality of Experience', 'NetPath', 'Virtual Infrastructure Monitor') 59 | ORDER BY Hostname 60 | -------------------------------------------------------------------------------- /Get-OrionServerVersionInformation.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | Get-OrionServerVersionInformation.ps1 4 | 5 | Gets the installed product versions of SolarWinds products installed in your infrastructure 6 | 7 | 8 | Results appear like this: 9 | 10 | Hostname ServerType Product Version 11 | -------- ---------- ------- ------- 12 | NOCKMSMPE01V MainPoller IP Address Manager 2020.2.1 13 | NOCKMSMPE01V MainPoller Log Analyzer 2020.2.1 14 | NOCKMSMPE01V MainPoller NetFlow Traffic Analyzer 2020.2.1 HF2 15 | NOCKMSMPE01V MainPoller Network Configuration Manager 2020.2.1 HF1 16 | NOCKMSMPE01V MainPoller Network Performance Monitor 2020.2.1 17 | NOCKMSMPE01V MainPoller Orion Platform 2020.2.1 HF1 18 | NOCKMSMPE01V MainPoller Server & Application Monitor 2020.2.1 HF1 19 | NOCKMSMPE01V MainPoller Server Configuration Monitor 2020.2.1 20 | NOCKMSMPE01V MainPoller Storage Resource Monitor 2020.2.1 21 | NOCKMSMPE01V MainPoller User Device Tracker 2020.2.1 HF1 22 | NOCKMSMPE01V MainPoller VoIP and Network Quality Manager 2020.2.1 23 | NOCKMSMPE01V MainPoller Web Performance Monitor 2020.2.1 24 | 25 | #> 26 | 27 | if ( -not ( $SwisConnection ) ) 28 | { 29 | $OrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the Orion Server" 30 | $SwisCredentials = Get-Credential -Message "Enter your Orion credentials for $OrionServer" 31 | $SwisConnection = Connect-Swis -Credential $SwisCredentials -Hostname $OrionServer 32 | } 33 | 34 | 35 | # Get the details for your Orion Servers 36 | 37 | $SwqlOrionServerData = @" 38 | SELECT HostName, 39 | ServerType, 40 | Details 41 | FROM Orion.OrionServers 42 | "@ 43 | 44 | # Build an empty report for the version information 45 | $VersionReport = @() 46 | 47 | # List of actual product names (ignoring "features" that appear like products 48 | $ProductNames = "Database Performance Analyzer Integration Module", "IP Address Manager", "Log Analyzer", "NetFlow Traffic Analyzer", "Network Configuration Manager", "Network Performance Monitor", "Orion Platform", "Server & Application Monitor", "Server Configuration Monitor", "Storage Resource Monitor", "Toolset", "User Device Tracker", "VoIP and Network Quality Manager", "Web Performance Monitor" 49 | $ProductAcronyms = "DPAIM", "IPAM", "LA", "NCM", "NPM", "NTA", "SAM", "SCM", "SRM", "Toolset", "UDT", "VMAN", "VNQM", "WPM" 50 | 51 | $ServerData = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlOrionServerData 52 | 53 | # Cycle through each server found 54 | ForEach ( $Server in $ServerData ) 55 | { 56 | # Get the information from the JSON blob 57 | ForEach ( $Product in ( $Server.Details | ConvertFrom-Json ) | Select-Object -Property Name, Version, HotfixVersionNumber | Sort-Object -Property Name, Version, HotfixVersionNumber ) 58 | { 59 | # Here's where we ignore the "features" that are listed like a product 60 | # if ( $Product.Name -in $ProductNames ) 61 | if ( $Product.ShortName -in $ProductAcryonyms ) 62 | { 63 | if ( $Product.HotfixVersionNumber ) 64 | { 65 | # if a hotfix exists, add that at the end of the the version number 66 | $VersionReport += New-Object -TypeName PSObject -Property ( [ordered]@{ Hostname = $Server.Hostname; ServerType = $Server.ServerType; Product = $Product.Name; Version = "$( $Product.Version ) HF$( $Product.HotfixVersionNumber )" } ) 67 | } 68 | else 69 | { 70 | # if it doesn't exist, just show the version number 71 | $VersionReport += New-Object -TypeName PSObject -Property ( [ordered]@{ Hostname = $Server.Hostname; ServerType = $Server.ServerType; Product = $Product.Name; Version = $Product.Version } ) 72 | } 73 | } 74 | } 75 | } 76 | # Output the report 77 | $VersionReport 78 | 79 | -------------------------------------------------------------------------------- /GroupsToCreate.csv: -------------------------------------------------------------------------------- 1 | url,name,status,region,facility,physical_address,latitude,longitude 2 | https://netbox_server.domain.com/ipshack/api/dcim/sites/41/?format=json,A1111,Active,Zone 1 - South,A1111 SiteName Here,"2111, some road, somewhere, ON, H0H 0H0",42.54021818,-77.6806524 3 | https://netbox_server.domain.com/ipshack/api/dcim/sites/308/?format=json,A1112,Active,Zone 1 - South,A1112 SiteName Here,"2112, some road, somewhere, ON, H0H 0H0",41.0896473,-80.7439152 4 | https://netbox_server.domain.com/ipshack/api/dcim/sites/1187/?format=json,A1113,Active,Zone 1 - South,A1113 SiteName Here,"2113, some road, somewhere, ON, H0H 0H0",42.07040273,-78.2282826 5 | https://netbox_server.domain.com/ipshack/api/dcim/sites/1432/?format=json,A1114,Active,Zone 1 - South,A1114 SiteName Here,"2114, some road, somewhere, ON, H0H 0H0",43.934241,-72.95332688 6 | https://netbox_server.domain.com/ipshack/api/dcim/sites/1445/?format=json,A1115,Active,Zone 1 - South,A1115 SiteName Here,"2115, some road, somewhere, ON, H0H 0H0",42.34154145,-78.42683355 7 | https://netbox_server.domain.com/ipshack/api/dcim/sites/1446/?format=json,A1116,Active,Zone 1 - South,A1116 SiteName Here,"2116, some road, somewhere, ON, H0H 0H0",42.6240204,-79.63149968 8 | https://netbox_server.domain.com/ipshack/api/dcim/sites/1482/?format=json,A1117,Active,Zone 1 - South,A1117 SiteName Here,"2117, some road, somewhere, ON, H0H 0H0",42.17265,-77.868375 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kevin M. Sparenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /ListResourcesOnNode.ps1: -------------------------------------------------------------------------------- 1 | # Asssumes you have authenticated and stored your info in $SwisConnection 2 | $NodeID = 1009 3 | $Job = Invoke-SwisVerb -SwisConnection $SwisConnection -Entity 'Orion.Nodes' -Verb 'ScheduleListResources' -Arguments $NodeID 4 | $Timer = New-Object -TypeName 'System.Diagnostics.Stopwatch' 5 | # Set an overall timeout 6 | $Timeout = 600 # seconds 7 | if ( $Job ) { 8 | # We got back job information - extract the JobId 9 | $JobID = $Job.InnerText 10 | # Validate that the jobID is in a GUID format 11 | if ( $JobID -match '[a-f,0-9,A-F]{8}-[a-f,0-9,A-F]{4}-[a-f,0-9,A-F]{4}-[a-f,0-9,A-F]{4}-[a-f,0-9,A-F]{12}' ) { 12 | # Starting the stopwatch 13 | $Timer.Restart() 14 | do { 15 | # if we go over time, just break out of the loop 16 | if ( $Timer.Elapsed.TotalSeconds -gt $Timeout ) { 17 | break 18 | } 19 | # Get the Job Status 20 | $JobStatus = Invoke-SwisVerb -SwisConnection $SwisConnection -Entity 'Orion.Nodes' -Verb 'GetScheduledListResourcesStatus' -Arguments $JobID, $NodeID 21 | # Pull the status text 22 | $Status = $JobStatus.InnerText 23 | 24 | # if the status text isn't ReadyForImport, sleep for 15 seconds. 25 | if ( $Status -ne 'ReadyForImport' ) { 26 | Write-Warning -Message "Current status is: $Status for $JobID / Waiting for 15 seconds and trying again" 27 | Start-Sleep -Seconds 15 28 | } 29 | } while ( $Status -ne 'ReadyForImport' ) 30 | # Stop the timer 31 | $Timer.Stop() 32 | if ( $Status = 'ReadyToImport' ) { 33 | # Import the Results 34 | Write-Host "Ready to import!" -ForegroundColor Green 35 | } 36 | else { 37 | Write-Error -Message "Timed out waiting for a result on on $NodeID / $JobID [Last Status: $Status]" 38 | } 39 | } 40 | } 41 | else { 42 | # Job request failed 43 | Write-Errors -Message "Unable to create a List Resources job for node with ID: $NodeID" 44 | } 45 | 46 | -------------------------------------------------------------------------------- /MutedElements.swql: -------------------------------------------------------------------------------- 1 | SELECT [Entities].DisplayName AS [Element] 2 | , [Entities].DetailsUrl AS [_LinkFor_Element] 3 | , [Entities].Status 4 | , [Entities].StatusDescription 5 | , CASE 6 | WHEN [Entities].InstanceType = 'Orion.Nodes' 7 | THEN 'Node' 8 | WHEN [Entities].InstanceType LIKE 'Orion.APM.%' 9 | THEN 'Application' 10 | WHEN [Entities].InstanceType = 'Orion.NPM.Interfaces' 11 | THEN 'Interface' 12 | WHEN [Entities].InstanceType = 'Orion.Groups' 13 | THEN 'Group' 14 | ELSE [Entities].InstanceType 15 | END AS [ElementType] 16 | , [MutedAlerts].SuppressFrom 17 | , [MutedAlerts].SuppressUntil 18 | FROM Orion.AlertSuppression AS [MutedAlerts] 19 | LEFT JOIN System.ManagedEntity AS [Entities] ON [MutedAlerts].EntityUri = [Entities].Uri 20 | -------------------------------------------------------------------------------- /MutedNodes.swql: -------------------------------------------------------------------------------- 1 | SELECT TOP 1000 [AuditEvent].AuditEventID 2 | , [AuditEvent].TimeLoggedUtc 3 | , [AuditEvent].AccountID 4 | , [AuditEvent].AuditingActionType.ActionTypeDisplayName 5 | , [AuditEvent].AuditEventMessage 6 | , [AuditEvent].DetailsUrl 7 | , [AuditEvent].DisplayName 8 | , [AuditEvent].Arguments.ArgsValue AS [EntityUri] 9 | FROM Orion.AuditingEvents AS [AuditEvent] 10 | --WHERE [AuditEvent].AuditingActionType.ActionTypeDisplayName = 'Alerts muted' 11 | WHERE [AuditEvent].Arguments.ArgsKey = 'EntityUri' 12 | ORDER BY [AuditEvent].AuditEventID DESC 13 | 14 | 15 | SELECT CASE 16 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%' AND [AlertSup].EntityUri NOT LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/%' THEN [N].Caption 17 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/Interfaces/InterfaceID=%' THEN [I].FullName 18 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/Applications/ApplicationID=%' THEN [AA].FullyQualifiedName 19 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Groups/ContainerID=%' THEN [G].Name 20 | END AS [EntityName] 21 | , CASE 22 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%' AND [AlertSup].EntityUri NOT LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/%' THEN [N].DetailsUrl 23 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/Interfaces/InterfaceID=%' THEN [I].DetailsUrl 24 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/Applications/ApplicationID=%' THEN [AA].DetailsUrl 25 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Groups/ContainerID=%' THEN [G].DetailsUrl 26 | END AS [DetailsUrl] 27 | , CASE 28 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%' AND [AlertSup].EntityUri NOT LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/%' THEN 'Node' 29 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/Interfaces/InterfaceID=%' THEN 'Interface' 30 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Nodes/NodeID=%/Applications/ApplicationID=%' THEN 'Application' 31 | WHEN [AlertSup].EntityUri LIKE 'swis://%/Orion/Orion.Groups/ContainerID=%' THEN 'Group' 32 | END AS [ElementType] 33 | , [AlertSup].SuppressFrom AS [Mute Time] 34 | FROM Orion.AlertSuppression AS [AlertSup] 35 | LEFT OUTER JOIN Orion.Nodes AS [N] 36 | ON [AlertSup].[EntityUri] = [N].[Uri] 37 | LEFT OUTER JOIN Orion.NPM.Interfaces AS [I] 38 | ON [AlertSup].[EntityUri] = [I].[Uri] 39 | LEFT OUTER JOIN Orion.APM.Application AS [AA] 40 | ON [AlertSup].[EntityUri] = [AA].[Uri] 41 | LEFT OUTER JOIN Orion.Container AS [G] 42 | ON [AlertSup].[EntityUri] = [G].[Uri] 43 | WHERE [AlertSup].SuppressFrom <= GETUTCDATE() -- Ignore mutings scheduled in the future 44 | ORDER BY [AlertSup].SuppressFrom -------------------------------------------------------------------------------- /NodeWithIPInfo.CustomQueryWidget.swql: -------------------------------------------------------------------------------- 1 | -- Tested on Orion Platform 2020.2.1 with NPM and IPAM minimum 2 | -- Place a Custom Query Widget on a Node Details page and paste in the below. 3 | -- I'm crediting this submission to Marc Netterfield (Mesverrum) [https://github.com/Mesverrum] because I'm 93.87% sure that he was the original author. 4 | 5 | 6 | SELECT -- Node Details 7 | '' AS [ ] 8 | , CONCAT([Nodes].NodeID,' ',1) AS [_LinkFor_ ] 9 | , 'Node' AS ObjectType 10 | , [Nodes].Caption AS Object 11 | , [Nodes].DetailsUrl AS [_LinkFor_Object] 12 | , [Status].StatusName AS Status 13 | , CONCAT('/Orion/images/StatusIcons/Small-', [Nodes].StatusIcon) AS [_IconFor_Status] 14 | , ToLocal([Nodes].LastSync) AS [Last Polled] 15 | , CONCAT([Nodes].Vendor, ' - ', [Nodes].MachineType) AS ExtraInfo 16 | FROM Orion.Nodes AS [Nodes] 17 | JOIN Orion.StatusInfo AS [Status] 18 | ON [Status].StatusID = [Nodes].Status 19 | WHERE [Nodes].NodeID = ${NodeID} 20 | 21 | UNION ( 22 | SELECT -- IPAM addresses 23 | '' AS [ ] 24 | , CONCAT([Nodes].NodeID, ' ', 2) AS [_LinkFor_ ] 25 | , CONCAT('IPAM ', [IP].IPType, ' Address') AS ObjectType 26 | , IsNull([IP].IPAddress ,'Not in IPAM') AS Object 27 | , [IP].DetailsUrl AS [_LinkFor_Object] 28 | , [IP].IPStatus AS Status 29 | , IsNull(('/Orion/IPAM/res/images/sw/icon.ip.'+ [IP].IPStatus +'.gif'),'/Orion/images/StatusIcons/Small-Down.gif') AS [_IconFor_Status] 30 | , [IP].LastSync AS LastScanned 31 | , CONCAT(ip.Comments, '') AS ExtraInfo 32 | FROM Orion.Nodes AS [Nodes] 33 | LEFT JOIN IPAM.IPNodeReport AS [IP] 34 | ON [Nodes].IP = [IP].IPAddress 35 | WHERE [Nodes].NodeID = ${NodeID} 36 | ) 37 | UNION ( 38 | SELECT --DHCP 39 | '' AS [ ] 40 | , CONCAT([Nodes].NodeID, ' ', 3) AS [_LinkFor_ ] 41 | , 'DHCP' AS ObjectType 42 | , IsNull([Lease].ClientName, 'Not in Monitored DHCP') AS Object 43 | , [IP].DetailsUrl AS [_LinkFor_Object] 44 | , [IP].IPStatus AS Status 45 | , IsNull( ('/Orion/IPAM/res/images/sw/icon.[IP].' + [IP].IPStatus + '.gif'), '/Orion/images/StatusIcons/Small-Down.gif') AS [_IconFor_Status] 46 | , [IP].LastSync AS LastScanned 47 | , CASE 48 | WHEN [Lease].ReservationType IS NOT NULL THEN CONCAT('Reservation ', [Lease].ClientMAC) 49 | ELSE '' 50 | END AS ExtraInfo 51 | FROM Orion.Nodes AS [Nodes] 52 | JOIN IPAM.IPNodeReport AS [IP] 53 | ON [Nodes].IP = [IP].IPAddress 54 | AND [Nodes].NodeID = ${NodeID} 55 | LEFT JOIN IPAM.DhcpLease AS [Lease] 56 | ON [Lease].ClientIpAddress = [IP].IPAddress 57 | WHERE [IP].IPType = 'Dynamic' 58 | AND [Nodes].NodeID = ${NodeID} 59 | ) 60 | UNION ( 61 | SELECT DISTINCT -- DNS Information 62 | '' AS [ ] 63 | , CONCAT([Nodes].nodeid,' ',4) AS [_LinkFor_ ] 64 | , 'DNS Host Record' AS ObjectType 65 | , IsNull([DNS].data, 'Not in Monitored DNS') AS Object 66 | , [IP].DetailsUrl AS [_LinkFor_Object] 67 | , CASE 68 | WHEN [DNS].name IS NULL THEN '' 69 | WHEN [DNS].name LIKE '%' + [Nodes].Caption + '%' THEN 'Matched' 70 | ELSE 'Possible DNS Mismatch' 71 | END AS Status 72 | , CASE 73 | WHEN [DNS].name IS NULL THEN '' 74 | WHEN [DNS].name LIKE '%' + [Nodes].Caption + '%' THEN '/Orion/images/ActiveAlerts/Check.png' 75 | ELSE '/Orion/images/ActiveAlerts/Serious.png' 76 | END AS [_IconFor_Status] 77 | , [DS].LastDiscovery AS LastScanned 78 | , CASE 79 | WHEN [DNS].name IS NOT NULL THEN CONCAT('Record ', [DNS].name, ' in zone ', [DZ].Name) 80 | ELSE '' 81 | END AS ExtraInfo 82 | FROM Orion.Nodes AS [Nodes] 83 | JOIN IPAM.IPNodeReport AS [IP] 84 | ON [Nodes].IP = [IP].IPAddress 85 | AND [Nodes].NodeID = ${NodeID} 86 | LEFT JOIN IPAM.DnsRecordReport AS [DNS] 87 | ON [DNS].Data = [IP].IPAddress 88 | AND [DNS].type IN (1) -- DNS A Record 89 | LEFT JOIN IPAM.DnsZone AS [DZ] 90 | ON [DZ].DnsZoneId = [DNS].DnsZoneId 91 | LEFT JOIN ( SELECT TOP 1 [DS].NodeId 92 | , MAX([DS].LastDiscovery) AS LastDiscovery 93 | FROM IPAM.DnsServer AS [DS] 94 | GROUP BY [DS].NodeID 95 | ORDER BY MAX([DS].LastDiscovery) DESC ) AS [DS] 96 | ON [DS].NodeID = [DZ].NodeID 97 | WHERE [Nodes].NodeID = ${NodeID} AND [DS].LastDiscovery IS NOT NULL 98 | ) 99 | UNION ( 100 | SELECT -- DHCP/Subnet Information 101 | '' AS [ ] 102 | , CONCAT([Nodes].NodeID, ' ', 5) AS [_LinkFor_ ] 103 | , 'IPAM ' + ISNULL([Subnet].GroupTypeText, '') + ' Group' AS ObjectType 104 | , IsNull([Subnet].FriendlyName,'Not in IPAM') AS Object 105 | , [Subnet].DetailsUrl AS [_LinkFor_Object] 106 | , [Subnet].StatusShortDescription AS Status 107 | , '/Orion/IPAM/res/images/sw/icon.subnet.' + [Subnet].StatusShortDescription + '.gif' AS [_IconFor_Status] 108 | , [Subnet].LastDiscovery AS LastScanned 109 | , CASE 110 | WHEN [Subnet].FriendlyName IS NULL THEN '' 111 | ELSE CONCAT([Subnet].UsedCount, '/', [Subnet].AllocSize, ' used| VLAN ', IsNull([Subnet].VLAN, 'Unknown') , '| Comment: ', [Subnet].Comments ) 112 | END AS ExtraInfo 113 | FROM Orion.Nodes AS [Nodes] 114 | JOIN IPAM.IPNodeReport AS [IP] 115 | ON [Nodes].ip = [IP].IPAddress 116 | LEFT JOIN IPAM.GroupReport AS [Subnet] 117 | ON [Subnet].GroupId = [IP].SubnetID 118 | WHERE [Nodes].NodeID = ${NodeID} 119 | ) 120 | ORDER BY [_LinkFor_ ] -------------------------------------------------------------------------------- /Ping-TraceRT.ps1: -------------------------------------------------------------------------------- 1 | <# File Name: Ping-TraceRT.ps1 #> 2 | # Adding a timer just to see how long this takes 3 | $Stopwatch = New-Object -TypeName System.Diagnostics.Stopwatch 4 | $Stopwatch.Start() 5 | 6 | # Move to the Proper Folder where these scripts 'live' 7 | Write-Host "Execution Path: $( ( Split-Path -Path $MyInvocation.MyCommand.Path -Parent ) )" 8 | Set-Location -Path ( Split-Path -Path $MyInvocation.MyCommand.Path -Parent ) 9 | 10 | # Check & Import the Test-PingTrace function 11 | if ( -not ( Get-Command -Name Test-PingTrace -ErrorAction SilentlyContinue) ) { 12 | . .\func_PingTrace.ps1 13 | } 14 | 15 | <# Expected Arguments (in order) 16 | IP Address (to do the ping and tracert) 17 | alertDefinitionId (up write the note) 18 | alertObject (for nodes, this is the Node ID) 19 | objectType (for nodes, this is 'Node') 20 | 21 | Matching SWIS Variable Definition: 22 | IP Address = '${N=SwisEntity;M=IP_Address}' 23 | alertDefinitonId = '${N=Alerting;M=AlertDefID}' 24 | alertObject = '${N=SwisEntity;M=NodeID}' 25 | objectType = '${N=Alerting;M=ObjectType}' 26 | 27 | The 'alert action executable' should look like this: 28 | 29 | "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy Unrestricted -File "C:\Scripts\Ping-TraceRT.ps1" "${N=SwisEntity;M=IP_Address}" "${N=Alerting;M=AlertDefID}" "${N=SwisEntity;M=NodeID}" "${N=Alerting;M=ObjectType}" 30 | 31 | This is assuming that you save the Ping-TraceRT.ps1 and func_PingTrace.ps1 in the "C:\Scripts" folder 32 | 33 | #> 34 | 35 | $IPAddress = $args[0] 36 | $alertDefinitionId = $args[1] 37 | $alertObject = $args[2] 38 | $objectType = $args[3] 39 | 40 | 41 | 42 | $TestResults = Test-PingTrace -IPAddress $IPAddress 43 | 44 | if ( $TestResults ) { 45 | $SwisConnection = Connect-Swis -Hostname "$env:COMPUTERNAME" -Certificate 46 | Invoke-SwisVerb -SwisConnection $SwisConnection -EntityName "Orion.AlertStatus" -Verb "AddNote" -Arguments ( $alertDefinitionId, $alertObject, $objectType, $TestResults ) | Out-Null 47 | } 48 | $Stopwatch.Stop() 49 | Write-Host "Complete Execution Time: $( $Stopwatch.Elapsed.TotalMinutes.ToString("0.00") ) minutes" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SolarWinds Query Language (SWQL) Query Playground 2 | 3 | ## This is a public repository for some of my PowerShell scripts and the associated SWQL queries 4 | 5 | **None of these queries are directly supported my myself nor SolarWinds. Use them at your own risk.** 6 | 7 | For more information about queries written in the SolarWinds Query Language (SWQL) you can find it in the [Orion SDK](https://github.com/solarwinds/OrionSDK) repository. 8 | 9 | This is my playground for SWQL Queries that I think are interesting. They are neither supported nor approved by SolarWinds directly. 10 | 11 | ### GENERAL DISCLAIMER 12 | 13 | I write all of my scripts using many comments and I attempt to use full parameterization for all function calls for easier readability for new users of PowerShell and the SolarWinds Orion SDK. 14 | 15 | I'm doing my best to use [Visual Studio Code](https://code.visualstudio.com) for all of my scripts. 16 | 17 | This is the list of the VS Code Extensions that I'm using: 18 | 19 | - Bracket Pair Colorizer 2 (v0.2.0) 20 | - C/C++ (v1.0.1) 21 | - C# (v1.23.4) 22 | - GitHub Pull Requests and Issues (v0.20.1) 23 | - GitLens - Git supercharged (v10.2.2) 24 | - HTML (C#) v0.1.3 25 | - IntelliSense for CSS class names in HTML (v1.19.0) 26 | - Jira and Bitbucket (Official) (v2.8.3) 27 | - Khoros (v1.0.0) 28 | - Live Server (v5.6.1) 29 | - markdownlint (v0.37.1) 30 | - PowerShell (v2020.6.0) 31 | - Prettier - Code formatter (v5.7.1) 32 | - Python (v2020.9.114305) 33 | - Rainbow CSV (v1.7.1) 34 | - Remote - WSL (v0.50.1) 35 | 36 | **Note that this is my *full* list, not just the ones that I use for projects in this repository.** 37 | -------------------------------------------------------------------------------- /Remove-InterfacesWithBadIfIndex.ps1: -------------------------------------------------------------------------------- 1 | #region Connect to SolarWinds Information Service 2 | if ( -not $SwisConnection ) { 3 | $Hostname = "orionserver.domain.local" 4 | $SwisConnection = Connect-Swis -Hostname $Hostname -Credential ( Get-Credential -Message "Enter your Orion credentials for $Hostname" ) 5 | } 6 | #endregion Connect to SolarWinds Information Service 7 | 8 | # Query to identify interfaces with bad a bad index 9 | $SwqlQuery = @" 10 | SELECT [Interfaces].Node.Caption AS [Node] 11 | , [Interfaces].Name AS [Name] 12 | , [Interfaces].Uri 13 | FROM Orion.NPM.Interfaces AS [Interfaces] 14 | WHERE [Interfaces].Index = -1 15 | ORDER BY [Interfaces].Node.Caption 16 | , [Interfaces].Name 17 | "@ 18 | 19 | # Store the interfaces as an array 20 | $IntsToDelete = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlQuery 21 | 22 | # Cycle through each, say what's being deleted and then delete it. 23 | ForEach ( $Int in $IntsToDelete ) { 24 | Write-Host "Removing $( $Int.Name ) from $( $Int.Node )" -ForegroundColor Red 25 | Remove-SwisObject -SwisConnection $SwisConnection -Uri $Int.Uri 26 | } 27 | -------------------------------------------------------------------------------- /SAM_CheckCertificatesWithExclusion.ps1: -------------------------------------------------------------------------------- 1 | #region Define Exit Codes 2 | $ExitCode = @{ "Up" = 0; 3 | "Down" = 1; 4 | "Warning" = 2; 5 | "Critical" = 3; 6 | "Unknown" = 4 7 | } 8 | #endregion Define Exit Codes 9 | 10 | # Getting the Parameters from the passed arguments 11 | # Expected Parameters: 12 | # first is an integer representing the number of days before execution 13 | # second through the end is a list of subjects to exclude 14 | if ( $args ) { 15 | $VerbosePreference = "SilentlyContinue" 16 | $intThreshold = $args[0] 17 | $excludeSubjects = @() 18 | For ( $i = 1; $i -lt $args.Count; $i++ ) { 19 | $excludeSubjects += $args[$i] 20 | } 21 | $LocalCreds = Get-Credential -UserName ${Username} 22 | $IpAddress = ${IP} 23 | } else { 24 | $VerbosePreference = "Continue" 25 | Write-Verbose -Message "Executing in 'Test' Mode with static options" 26 | $intThreshold = 60 # days 27 | $excludeSubjects = "Verisign", "Microsoft" 28 | $LocalCreds = Get-Credential 29 | $IpAddress = "192.168.21.101" 30 | 31 | $testMode = $true 32 | } 33 | 34 | # Lookup the target server name from DNS 35 | $HostNames = [System.Net.Dns]::GetHostByAddress($IpAddress) 36 | if ( $HostNames ) { 37 | # Use the first entry from hostnames, and use only the computername (strip off everything after the first .) 38 | $TargetServer = $HostNames[0].HostName.Split(".")[0].ToUpper() 39 | } 40 | 41 | Write-Verbose -Message "Setting Deadline date to: $( ( Get-Date ).AddDays($intThreshold ) )" 42 | $dateDeadline = ( Get-Date ).AddDays($intThreshold) 43 | 44 | # Currently setup to run on Orion server against a target server (Invoke-Command) 45 | $objStore = Invoke-Command -ComputerName $TargetServer -Credential $LocalCreds -ScriptBlock { Get-ChildItem -Path 'Cert:\LocalMachine\Root' } 46 | # Add a member that'll present the name in an easier way 47 | $objStore | Add-Member -MemberType ScriptProperty -Name "Name" -Value { ( $this.Subject.Split(",") | ForEach-Object { $_.Trim().Split("=")[1] } ) -join ", " } -Force 48 | # add a member so I can filter for those already expired 49 | $objStore | Add-Member -MemberType ScriptProperty -Name "IsExpired" -Value { $this.NotAfter -lt ( Get-Date ) } -Force 50 | # add a member so I can filter for those with upcoming expiration 51 | $objStore | Add-Member -MemberType ScriptProperty -Name "IsUpcomingExpiration" -Value { $this.NotAfter -lt $dateDeadline } -Force 52 | 53 | Write-Verbose -Message "Original Store has: $( $objStore.Count ) entrie(s)" 54 | $cleanStore = $objStore 55 | ForEach ( $excludeSubject in $excludeSubjects) { 56 | Write-Verbose -Message "Filtering off '$excludeSubject' from Certificate List" 57 | $cleanStore = $cleanStore | Where-Object { $_.Subject -notlike "*$excludeSubject*" } 58 | } 59 | Write-Verbose -Message "Filtered Store has: $( $cleanStore.Count ) entrie(s)" 60 | 61 | # Build objects to make creating the output easier 62 | $expiredCertificates = $cleanStore | Where-Object { $_.IsExpired } 63 | $upcomingExpirationCertificates = $cleanStore | Where-Object { ( -not ( $_.IsExpired ) ) -and ( $_.IsUpcomingExpiration ) } 64 | $validCertificates = $cleanStore | Where-Object { ( -not ( $_.IsExpired ) ) -and ( -not ( $_.IsUpcomingExpiration ) ) } 65 | 66 | # Do you want to include the certificate names? This can make the messages VERY long 67 | $IncludeCertNames = $false 68 | if ( $IncludeCertNames ) { 69 | $expiredList = " [Certificate List: $( $expiredCertificates.Name -join "; " )]" 70 | $upcomingList = " [Certificate List: $( $upcomingExpirationCertificates.Name -join "; " )]" 71 | $excludeList = " [Ignored Subjects: $( $excludeSubjects -join "; " )]" 72 | } else { 73 | $expiredList = "" 74 | $upcomingList = "" 75 | $excludeList = "" 76 | } 77 | Write-Host "Message.Upcoming: $( $upcomingExpirationCertificates.Count ) certificate(s) on '$TargetServer' are expiring in the next $intThreshold days.$upcomingList" 78 | Write-Host "Statistic.Upcoming: $( $upcomingExpirationCertificates.Count )" 79 | Write-Host "Message.Expired: $( $expiredCertificates.Count ) certificate(s) on '$TargetServer' are already expired.$expiredList" 80 | Write-Host "Statistic.Expired: $( $expiredCertificates.Count )" 81 | Write-Host "Message.Valid: $( $validCertificates.Count ) certificate(s) on '$TargetServer' are valid." 82 | Write-Host "Statistic.Valid: $( $validCertificates.Count )" 83 | Write-Host "Message.Ignored: Ignoring $( $objStore.Count - $cleanStore.Count ) certificate(s) on '$TargetServer'$excludeList" 84 | Write-Host "Statistic.Ignored: $( $objStore.Count - $cleanStore.Count )" 85 | 86 | if ( -not $testMode ) { 87 | exit $ExitCode['Up'] 88 | } 89 | -------------------------------------------------------------------------------- /SamTemplates/AmazonRDSTemplate.ps1: -------------------------------------------------------------------------------- 1 | #region Define Exit Codes 2 | $ExitCode = @{ "Up" = 0; 3 | "Down" = 1; 4 | "Warning" = 2; 5 | "Critical" = 3; 6 | "Unknown" = 4 } 7 | #endregion Define Exit Codes 8 | 9 | #region Build Metric List (not all are used for each script monitor 10 | $MetricNames = @" 11 | DisplayName,Name,Statistics,Unit,DisplayUnit,Type,Ranking 12 | CPU Utilization,CPUUtilization,Average,Percent,%,Compute,1 13 | Database Connections,DatabaseConnections,Sum,Count,,Database,2 14 | Disk Queue Depth,DiskQueueDepth,Sum,Count,,Storage,4 15 | Freeable Memory,FreeableMemory,Average,Bytes,bytes,Compute,1 16 | Free Storage Space,FreeStorageSpace,Average,Bytes,bytes,Storage,4 17 | Network Receive Throughput,NetworkReceiveThroughput,Average,Bytes/Second,bytes/second,Network,3 18 | Network Transmit Throughput,NetworkTransmitThroughput,Average,Bytes/Second,bytes/second,Network,3 19 | Read IOPS,ReadIOPS,Average,Count/Second,operations/second,Storage,4 20 | Write IOPS,WriteIOPS,Average,Count/Second,operations/second,Storage,4 21 | Read Letency,ReadLatency,Average,Seconds,seconds,Storage,4 22 | Write Latency,WriteLatency,Average,Seconds,seconds,Storage,4 23 | Read Throughput,ReadThroughput,Average,Bytes/Second,bytes/second,Storage,4 24 | Write Throughput,WriteThroughput,Average,Bytes/Second,bytes/second,Storage,4 25 | Swap Usage,SwapUsage,Average,Bytes,bytes,Compute,1 26 | "@ 27 | 28 | # Convert to objects based on the above raw data 29 | $Metrics = $MetricNames | ConvertFrom-Csv 30 | 31 | # Sort them 32 | $Metrics = $Metrics | Sort-Object -Property Given | Sort-Object -Property Ranking 33 | #endregion Build Metric List (not all are used for each script monitor 34 | 35 | 36 | #region Retrieve and store passed arguments 37 | # Get script argument EX: , , , , [TimeRange], [Period], [Retries], [WaitTime] 38 | $Type = $args[0] # Parameter 1: Metric Type: Compute, Database, Network, Storage 39 | $AccessKey = $args[1] # Parameter 2: Access Key 40 | $SecretKey = $args[2] # Parameter 3: Secret Key 41 | $Region = $args[3] # Parameter 4: AWS Region 42 | $Database = $args[4] # Parameter 5: Database Name 43 | # Optional parameters 44 | $TimeRange = $args[5] # Parameter 6: Time Range (in minutes) [to be converted to seconds later] 45 | $Period = $args[6] # Parameter 7: Period in Time Range 46 | $GlobalRetries = $args[7] # Parameter 8: Number of Retries 47 | $WaitTime = $args[8] # Parameter 9: Wait Time between retries (in minutes) 48 | #endregion Retrieve and store passed arguments 49 | 50 | #region Generate Profile Name 51 | $UniqueProfileId = Get-Random -Minimum 10000 -Maximum 99999 52 | 53 | $ProfileName = "AWSRDS_SAM_$( $UniqueProfileId )" # Arbitrary file name to save the connection information 54 | #endregion Generate Profile Name 55 | 56 | 57 | #region check for valid values and revert to default if not acceptable 58 | if ( -not $TimeRange ) { 59 | $TimeRange = 10 60 | } 61 | 62 | if ( -not $Period ) { 63 | $Period = 2 64 | } 65 | 66 | if ( -not $GlobalRetries) { 67 | $GlobalRetries = 3 68 | } 69 | 70 | if ( -not $WaitTime) { 71 | $WaitTime = 0.5 72 | } 73 | #endregion check for valid values and revert to default if not acceptable 74 | 75 | #region Check for AWSPowerShell Module 76 | if ( -not ( Get-Module -ListAvailable -Name AWSPowerShell -ErrorAction SilentlyContinue ) ) { 77 | write-host ("Importing AWS Module.....") 78 | try { 79 | Import-Module AWSPowerShell -ErrorAction Stop -WarningAction SilentlyContinue 80 | } 81 | catch { 82 | Write-Host "[ERROR] $( $_.Exception.Message )" 83 | exit $ExitCode["Down"] 84 | } 85 | } 86 | #region Check for AWSPowerShell Module 87 | 88 | #region Connect to AWS 89 | $ConnectionRetries = $GlobalRetries 90 | while ( $ConnectionRetries ) 91 | { 92 | try { 93 | Set-AWSCredential -AccessKey $AccessKey -SecretKey $SecretKey -StoreAs $ProfileName -ErrorAction Stop -WarningAction SilentlyContinue 94 | Initialize-AWSDefaults -ProfileName $ProfileName -Region $Region -ErrorAction Stop -WarningAction SilentlyContinue 95 | Set-AWSCredentials -ProfileName $ProfileName -ErrorAction Stop -WarningAction SilentlyContinue 96 | } 97 | catch{ 98 | Write-Host "[ERROR] $($_.Exception.Message)" 99 | $ConnectionRetries -= $GlobalRetries 100 | if ( $ConnectionRetries -le 0 ) { 101 | write-host ("Error while connecting") 102 | exit $ExitCode["Down"] 103 | } 104 | } 105 | Start-Sleep -Seconds $WaitTime 106 | Write-Warning -Message "Trying to reconnect to AWS" 107 | } 108 | #endregion Connect to AWS 109 | 110 | #region Get the metrics 111 | # Define the time range 112 | # End Time is "right now" 113 | $EndTime = ( Get-Date ).ToUniversalTime() 114 | # Start Time is X minutes ago as defined by the argument passed to the $Time variable 115 | $StartTime = $EndTime.AddMinutes(-$TimeRange) 116 | 117 | # The AWS function wants the period in seconds, so we'll convert that 118 | $PeriodInSeconds = $Period * 60 119 | 120 | $SamOutput = @() 121 | ForEach ( $Metric in $Metrics | Where-Object { $_.Type -eq $Type } ) 122 | { 123 | $DataRetries = $GlobalRetries 124 | while ( $DataRetries ) { 125 | try { 126 | $Data = Get-CWMetricStatistic -Namespace 'AWS/RDS' -Dimension @{ Name = ”DBInstanceIdentifier”; Value = $Database } -MetricName $Metric.Name -UtcStartTime $StartTime -UtcEndTime $EndTime -Period $PeriodInSeconds -Statistics $Metric.Statistics -Unit $Metric.Unit -ErrorAction Stop -WarningAction SilentlyContinue 127 | $DataPoint = $Data.DataPoints | Sort-Object -Property Timestamp -Descending | Select-Object -First 1 | Select-Object -ExpandProperty $Metric.Statistics 128 | $DataPoint = [math]::Round($DataPoint, 2) 129 | $SamOutput += [PSObject]@{ Identifier = $Metric.Name; Statistic = $DataPoint; Message = "$( $Metric.DisplayName ) for '$Database' is $DataPoint $( $Metric.DisplayUnit )" } 130 | } 131 | catch { 132 | Write-Host "[ERROR] $( $_.Exception.Message )" 133 | $DataRetries-- 134 | if ( $DataRetries -le 0 ) { 135 | write-host ("Error while fetching the metric") 136 | exit $ExitCode["Down"] 137 | } 138 | Start-Sleep -Seconds $waitTime 139 | write-host ("Retrying to fetch the data") 140 | } 141 | finally { 142 | ForEach ( $Output in $SamOutput ) { 143 | Write-Host "Message.$( $Output.Identifier ): $( $Output.Message )" 144 | Write-Host "Statistic.$( $Output.Identifier ): $( $Output.Statistic )" 145 | } 146 | #Cleanup profile information 147 | Get-AwsCredential -ProfileName $ProfileName | Remove-AWSCredentialProfile -ErrorAction SilentlyContinue 148 | 149 | exit $ExitCode["Up"] 150 | } 151 | } 152 | } 153 | #endregion Get the metrics 154 | 155 | 156 | -------------------------------------------------------------------------------- /Set-EqualColumnWidth.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | Simple script to convert all columns on views to have the same width 4 | 5 | #> 6 | 7 | $SwqlQuery = @" 8 | SELECT ViewID 9 | , ViewKey 10 | , ViewTitle 11 | , ViewGroupName 12 | , ViewGroup 13 | , ViewType 14 | , ViewGroupPosition 15 | , ViewIcon 16 | , Columns 17 | , Column1Width 18 | , Column2Width 19 | , Column3Width 20 | , Column4Width 21 | , Column5Width 22 | , Column6Width 23 | , System 24 | , Customizable 25 | , LimitationID 26 | , NOCView 27 | , NOCViewRotationInterval 28 | , Uri 29 | FROM Orion.Views 30 | WHERE Columns <> 0 31 | 32 | "@ 33 | 34 | if ( -not ( $SwisConnection ) ) { 35 | $OrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for the Orion Server" 36 | $SwisCredentials = Get-Credential -Message "Enter your Orion credentials for $OrionServer" 37 | $SwisConnection = Connect-Swis -Credential $SwisCredentials -Hostname $OrionServer 38 | } 39 | 40 | $DisplayWidth = 1750 # pixels wide 41 | 42 | $Views = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlQuery 43 | ForEach ( $View in $Views ) { 44 | $EvenColumnWidth = [math]::Floor($DisplayWidth / $View.Columns) 45 | # Cycle through each column 46 | for ( $i = 1; $i -le 6; $i++ ) { 47 | $FieldName = "Column$( $i )Width" 48 | # check to see if a column is used 49 | if ( $i -le $View.Columns ) { 50 | # if yes, then set the width 51 | $View.$FieldName = $EvenColumnWidth 52 | } else { 53 | # if not, set it to NULL 54 | $View.$FieldName = $null 55 | } 56 | } 57 | $ViewProperties = @{ 58 | Column1Width = $View.Column1Width 59 | Column2Width = $View.Column2Width 60 | Column3Width = $View.Column3Width 61 | Column4Width = $View.Column4Width 62 | Column5Width = $View.Column5Width 63 | Column6Width = $View.Column6Width 64 | } 65 | Write-Host "Updating the columns widths on $( $View.ViewTitle ) [$( $View.ViewID )] to $EvenColumnWidth px" 66 | Set-SwisObject -SwisConnection $SwisConnection -Uri $View.Uri -Properties $ViewProperties 67 | } -------------------------------------------------------------------------------- /TopApplicationAvailabilityByTemplate.swql: -------------------------------------------------------------------------------- 1 | SELECT ApplicationName 2 | , AVG(PercentAvailability) AS AvgAvailability 3 | FROM ( 4 | SELECT MAX(ApplicationStatus.Timestamp) AS [Month] 5 | ,Nodes.Caption AS [NodeName] 6 | ,Nodes.DetailsUrl AS [NodesDetailsUrl] 7 | ,Application.Name AS [ApplicationName] 8 | ,Application.DetailsUrl AS [ApplicationDetailsUrl] 9 | ,SUM(ApplicationStatus.PercentAvailability * ApplicationStatus.RecordCount) / SUM(ApplicationStatus.RecordCount) AS [PercentAvailability] 10 | FROM Orion.APM.ApplicationStatus AS [ApplicationStatus] 11 | ,Orion.APM.Application AS [Application] 12 | ,Orion.Nodes AS [Nodes] 13 | WHERE ApplicationStatus.Timestamp >= AddMinute(MinuteDiff(GetDate(), GetUtcDate()), AddMonth(MonthDiff(0, GetDate()), 0)) 14 | AND ApplicationStatus.ApplicationID = Application.ApplicationID 15 | AND Application.NodeID = Nodes.NodeID 16 | GROUP BY Application.ApplicationID 17 | ,Application.Name 18 | ,Nodes.Caption 19 | ,NodesDetailsUrl 20 | ,ApplicationDetailsUrl 21 | ORDER BY Nodes.Caption 22 | ,Application.Name 23 | ) 24 | GROUP BY ApplicationName 25 | -------------------------------------------------------------------------------- /Update-Captions.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | File: Update-Captions.ps1 3 | Purpose: The is a quick script that I wrote to update Node Captions within Orion. It currently is set to remove any domain names (keeping only the hostname) 4 | and capitalize those captions. 5 | 6 | Futures: I'd like to convert this to a more well-rounded function instead of a stand alone script, but I need help in understanding what parameters should be included. 7 | 8 | Version History: 1.0 - first upload 9 | 10 | GENERAL DISCLAIMER: 11 | I write all of my scripts using frequent comments and I attempt to use full parameterization for all function calls for easier readability for new users. 12 | #> 13 | 14 | # Filter and rename tasks 15 | $HostNameOnly = $true 16 | $Capitalize = $true 17 | 18 | if ( -not ( $SwisConnection ) ) 19 | { 20 | $OrionServer = Read-Host -Prompt "Please enter the DNS name or IP Address for your Orion Server" 21 | $SwisCredentials = Get-Credential -Message "Enter your Orion credentials for $OrionServer" 22 | $SwisConnection = Connect-Swis -Credential $SwisCredentials -Hostname $OrionServer 23 | } 24 | 25 | # Base Query for Nodes 26 | # We really only need the Caption and the Uri, but I prefer to have a little extra. 27 | $SwqlQuery = @" 28 | SELECT [Nodes].NodeID 29 | , [Nodes].Uri 30 | , [Nodes].Caption 31 | , [Nodes].IPAddress 32 | FROM Orion.Nodes AS [Nodes] 33 | "@ 34 | 35 | #Build an empty collection for where clauses for the query 36 | # These clauses will be put together with " OR " as the joiner, so include any "AND" statements within the logic 37 | # For readability, I'm going to enclose any compound filters within parenthesis. 38 | $WhereClauses = @() 39 | 40 | if ( $HostNameOnly ) 41 | { 42 | # We need to filter for captions that match a domain name format: hostname.domain.local 43 | # Fair warning - if you are using an IPv4 address as a caption, this will also match. 44 | # We're trying to get around this by checking to see if the caption matches the IP, but no guarantees. 45 | 46 | $WhereClauses = "( [Nodes].Caption LIKE '%.%.%' AND [Nodes].Caption <> [Nodes].IPAddress )" 47 | } 48 | 49 | # If there are any where clauses, then let's put them together and add it to our base query 50 | if ( $WhereClauses ) 51 | { 52 | # if there are clauses, then we need the "WHERE" keyword and a space for separation. 53 | # I'm choosing to put it on a separate line (`n) because I may want the $SwqlQuery more readable, but it doesn't matter for function 54 | $SwqlQuery += "`nWHERE " 55 | # Join each of the WHERE clauses together with an " OR " (if we only have one, the -join does nothing) 56 | # Add it to the existing query string 57 | $SwqlQuery += $WhereClauses -join " OR " 58 | } 59 | 60 | $NodesToRename = Get-SwisData -SwisConnection $SwisConnection -Query $SwqlQuery 61 | Write-Host "Found $( $NodesToRename.Count) node caption(s) to rename" 62 | 63 | $NodesToRename | Add-Member -MemberType NoteProperty -Name NewCaption -Value "" -Force 64 | # Copy Caption into NewCaption 65 | ForEach ( $Node in $NodesToRename ) 66 | { 67 | $Node.NewCaption = $Node.Caption 68 | } 69 | 70 | if ( $Capitalize ) 71 | { 72 | ForEach ( $Node in $NodesToRename ) 73 | { 74 | $Node.NewCaption = $Node.NewCaption.ToUpper() 75 | } 76 | } 77 | 78 | if ( $HostNameOnly ) 79 | { 80 | ForEach ( $Node in $NodesToRename ) 81 | { 82 | $Node.NewCaption = $Node.NewCaption.Split(".")[0] 83 | } 84 | } 85 | 86 | # Remove any entries where Caption and NewCaption match (this shouldn't be necessary, but better safe) 87 | # I'm using the "-cne" operator instead of the "-ne" because I want this case sensitive. 88 | $NodesToRename = $NodesToRename | Where-Object { $_.Caption -cne $_.NewCaption } 89 | 90 | 91 | # to actually do the rename 92 | ForEach ( $Node in $NodesToRename ) 93 | { 94 | Write-Host "Updating caption on $( $Node.Caption ) to $( $Node.NewCaption )" 95 | # The parameter for -Properties is a hashtable of the field we want to update (Caption) and the value for it ($Node.NewCaption) 96 | Set-SwisObject -SwisConnection $SwisConnection -Uri $Node.Uri -Properties @{ Caption = $Node.NewCaption } -Verbose 97 | } -------------------------------------------------------------------------------- /Update-OrionServerNodeIds.ps1: -------------------------------------------------------------------------------- 1 | <################################################## 2 | Update-OrionServerNodeIds.ps1 3 | 4 | This script will update the Orion.OrionServers elements with the matching NodeIDs 5 | 6 | It prompts for confirmation on each update since there's a chance there will be a mismatch. 7 | ---Tested with Core 2020.2.6 HF2--- 8 | ##################################################> 9 | 10 | if ( -not ( $SwisConnection ) ) { 11 | $SwisHost = Read-Host -Prompt "Provide the IP or FQDN of your Orion server" 12 | 13 | if ( -not ( $SwisCreds ) ) { 14 | $SwisCreds = Get-Credential -Message "Enter your Orion credentials for $SwisHost" 15 | } 16 | 17 | if ( $SwisHost -and $SwisCreds ) { 18 | $SwisConnection = Connect-Swis -Hostname $SwisHost -Credential $SwisCreds 19 | } 20 | } 21 | 22 | $MissingNodeIds = Get-SwisData -SwisConnection $SwisConnection -Query "SELECT Uri, HostName FROM Orion.OrionServers WHERE IsNull(NodeID, 0) = 0" 23 | if ( $MissingNodeIds ) { 24 | ForEach ( $MissingNodeId in $MissingNodeIds ) { 25 | Write-Host "Checking matches for '$( $MissingNodeId.HostName )'" 26 | $PossibleMatches = Get-SwisData -SwisConnection $SwisConnection -Query "SELECT NodeID, Caption, DNS, SysName, IPAddress FROM Orion.Nodes WHERE Caption LIKE '%$( $MissingNodeId.HostName )%' OR DNS LIKE '%$( $MissingNodeId.HostName )%' OR SysName LIKE '%$( $MissingNodeId.HostName )%'" 27 | 28 | if ( $PossibleMatches.Count -eq 1 ) { 29 | # Single match 30 | Write-Host "`tFound a potential match:" 31 | $PossibleMatches | Format-Table 32 | 33 | # Build menu for choice 34 | $Yes = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList ( '&Yes', "Set NodeID for $( $MissingNodeId.HostName ) to $( $PossibleMatches.NodeID )" ) 35 | $No = New-Object -TypeName System.Management.Automation.Host.ChoiceDescription -ArgumentList ( '&No', "Make no changes") 36 | 37 | $Choices = [System.Management.Automation.Host.ChoiceDescription[]]($Yes, $No) 38 | $Title = "Update Orion.Servers Entry" 39 | $Message = "Do you want to update entry for '$( $MissingNodeId.HostName )' in Orion.OrionServers with NodeID: $( $PossibleMatches.NodeID )?" 40 | $Response = $Host.Ui.PromptForChoice($Title, $Message, $Choices, 1) 41 | # Check to see if we said "Yes" (Response 0) 42 | if ( $Response -eq 0 ) { 43 | Write-Host "We'd run the update now" -ForegroundColor Green 44 | Write-Host 'Command Code: Set-SwisObject -SwisConnection $SwisConnection -Uri $( $MissingNodeId.Uri ) -Properties @{ NodeId = $PossibleMatches.NodeID }' -ForegroundColor Green 45 | Write-Host "Command that's sent: Set-SwisObject -SwisConnection `$SwisConnection -Uri $( $MissingNodeId.Uri ) -Properties `@{ NodeId = $( $PossibleMatches.NodeID ) }" -ForegroundColor Green 46 | } 47 | else { 48 | Write-Host "I guess not you aren't interested in doing the update" -ForegroundColor Red 49 | } 50 | } 51 | elseif ( $PossibleMatches.Count -gt 1 ) { 52 | # Multiple matches 53 | Write-Host "`tFound multiple potential matches:" 54 | $PossibleMatches | Format-Table 55 | $Response = $null 56 | do { 57 | if ( -not $ResponseOk ) { 58 | if ( $Response ) { 59 | Write-Error -Message "'$Response' is an invalid NodeID. Please enter a Node ID from the below table." 60 | } 61 | $PossibleMatches | Format-Table -AutoSize 62 | } 63 | Write-Host "Enter the NodeID you'd like to assign to '$( $MissingNodeID.HostName )' in Orion.OrionServers " -NoNewLine 64 | $Response = Read-Host -Prompt "or enter 'S' to skip" 65 | $ResponseOk = $Response -in ( $PossibleMatches.NodeID ) -or $Response -eq 's' 66 | } until ( $ResponseOk ) 67 | if ( $Response.ToLower() -ne 's' ) { 68 | Write-Host "We'd run the update now" -ForegroundColor Green 69 | Write-Host 'Command Code: Set-SwisObject -SwisConnection $SwisConnection -Uri $( $MissingNodeId.Uri ) -Properties @{ NodeId = $Response }' -ForegroundColor Green 70 | Write-Host "Command that's sent: Set-SwisObject -SwisConnection `$SwisConnection -Uri $( $MissingNodeId.Uri ) -Properties `@{ NodeId = $Response }" -ForegroundColor Green 71 | } 72 | else { 73 | Write-Host "I guess not." -ForegroundColor Red 74 | } 75 | } 76 | else { 77 | # No matches 78 | Write-Error -Message "Found no appropriate matches. Are you monitoring your Orion servers?" 79 | } 80 | 81 | 82 | 83 | } 84 | } 85 | else { 86 | Write-Warning -Message "All Orion Server objects have a matching NodeID" 87 | } -------------------------------------------------------------------------------- /UtilityFunctions/func_Remove-InvalidFileNameChars.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.5.1 4 | 5 | .GUID fb77c199-25b8-4a26-ad12-300aa633d9ee 6 | 7 | .AUTHOR Chris Carter 8 | 9 | .COMPANYNAME 10 | 11 | .COPYRIGHT 2016 Chris Carter 12 | 13 | .TAGS RegularExpression StringFormatting InvalidFileNameCharacters 14 | 15 | .LICENSEURI http://creativecommons.org/licenses/by-sa/4.0/ 16 | 17 | .PROJECTURI https://gallery.technet.microsoft.com/Remove-Invalid-Characters-39fa17b1 18 | 19 | .ICONURI 20 | 21 | .EXTERNALMODULEDEPENDENCIES 22 | 23 | .REQUIREDSCRIPTS 24 | 25 | .EXTERNALSCRIPTDEPENDENCIES 26 | 27 | .RELEASENOTES 28 | The parameter RemoveOnly has been added. This will exempt certain characters from being replaced with the Replacement string, and they will simply be removed. 29 | 30 | #> 31 | 32 | <# 33 | .SYNOPSIS 34 | Removes characters from a string that are not valid in Windows file names. 35 | 36 | .DESCRIPTION 37 | Remove-InvalidFileNameChars accepts a string and removes characters that are invalid in Windows file names. It then outputs the cleaned string. By default the space character is ignored, but can be included using the RemoveSpace parameter. 38 | 39 | The Replacement parameter will replace the invalid characters with the specified string. Its companion RemoveOnly will exempt given invalid characters from being replaced, and will simply be removed. Charcters in this list can be given as a string or their decimal or hexadecimal representation. 40 | 41 | The Name parameter can also clean file paths. If the string begins with "\\" or a drive like "C:\", it will then treat the string as a file path and clean the strings between "\". This has the side effect of removing the ability to actually remove the "\" character from strings since it will then be considered a divider. 42 | .PARAMETER Name 43 | Specifies the file name to strip of invalid characters. 44 | 45 | .PARAMETER Replacement 46 | Specifies the string to use as a replacement for the invalid characters. 47 | 48 | .PARAMETER RemoveOnly 49 | Specifes the list of invalid characters to remove from the string instead of being replaced by the Replacement parameter value. This may be given as one character strings, or their decimal or hexidecimal values. 50 | 51 | .PARAMETER RemoveSpace 52 | The RemoveSpace parameter will include the space character (U+0020) in the removal process. 53 | 54 | .INPUTS 55 | System.String 56 | Remove-InvalidFileNameChars accepts System.String objects in the pipeline. 57 | 58 | Remove-InvalidFileNameChars accepts System.String objects in a property Name from objects in the pipeline. 59 | 60 | .OUTPUTS 61 | System.String 62 | 63 | .EXAMPLE 64 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" 65 | Output: This name is an illegal filename.txt 66 | 67 | This command will strip the invalid characters from the string and output a clean string. 68 | .EXAMPLE 69 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" -RemoveSpace 70 | Output: Thisnameisanillegalfilename.txt 71 | 72 | This command will strip the invalid characters from the string and output a clean string, removing the space character (U+0020) as well. 73 | .EXAMPLE 74 | PS C:\> Remove-InvalidFileNameChars -Name '\\Path/:|?*<\With:*?>\:Illegal /Characters>?*.txt"' 75 | Output: \\Path\With\Illegal Characters.txt 76 | 77 | This command will strip the invalid characters from the path and output a valid path. Note: it would not be able to remove the "\" character. 78 | .EXAMPLE 79 | PS C:\> Remove-InvalidFileNameChars -Name '\\Path/:|?*<\With:*?>\:Illegal /Characters>?*.txt"' -RemoveSpace 80 | Output: \\Path\With\IllegalCharacters.txt 81 | 82 | This command will strip the invalid characters from the path and output a valid path, also removing the space character (U+0020) as well. Note: it would not be able to remove the "\" character. 83 | .EXAMPLE 84 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" -Replacement + 85 | Output: +This +name +is+ an +illegal +filename+.txt 86 | 87 | This command will strip the invalid characters from the string, replacing them with a "+", and outputting the result string. 88 | .EXAMPLE 89 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" -Replacemet + -RemoveOnly "*", 58, 0x3f 90 | Output: +This +name +is an illegal filename+.txt 91 | 92 | This command will strip the invalid characters from the string, replacing them with a "+", except the "*", the charcter with a decimal value of 58 (:), and the character with a hexidecimal value of 0x3f (?). These will simply be removed, and the resulting string output. 93 | .NOTES 94 | Author: Chris Carter 95 | Version: 1.5.1 96 | Last Updated: August 8, 2016 97 | 98 | .Link 99 | System.RegEx 100 | .Link 101 | about_Join 102 | .Link 103 | about_Operators 104 | #> 105 | 106 | #Requires -Version 2.0 107 | function Remove-InvalidFileNameChars { 108 | 109 | [CmdletBinding( 110 | DefaultParameterSetName = "Normal", 111 | HelpURI = 'https://gallery.technet.microsoft.com/scriptcenter/Remove-Invalid-Characters-39fa17b1' 112 | )] 113 | 114 | Param( 115 | [Parameter(Mandatory = $true, 116 | Position = 0, 117 | ValueFromPipeline = $true, 118 | ValueFromPipelineByPropertyName = $true, 119 | ParameterSetName = "Normal")] 120 | [Parameter(Mandatory = $true, 121 | Position = 0, 122 | ValueFromPipeline = $true, 123 | ValueFromPipelineByPropertyName = $true, 124 | ParameterSetName = "Replace")] 125 | [string[]]$Name, 126 | 127 | [Parameter(Mandatory = $true, 128 | Position = 1, 129 | ParameterSetName = "Replace")] 130 | [string]$Replacement, 131 | 132 | [Parameter(Position = 2, 133 | ParameterSetName = "Replace")] 134 | [Alias("RO")] 135 | [object[]]$RemoveOnly, 136 | 137 | [Parameter(ParameterSetName = "Normal")] 138 | [Parameter(ParameterSetName = "Replace")] 139 | [Alias("RS")] 140 | [switch]$RemoveSpace 141 | ) 142 | 143 | Begin { 144 | #Get an array of invalid characters 145 | $arrInvalidChars = [System.IO.Path]::GetInvalidFileNameChars() 146 | 147 | #Cast into a string. This will include the space character 148 | $invalidCharsWithSpace = [RegEx]::Escape( [string]$arrInvalidChars ) 149 | 150 | #Join into a string. This will not include the space character 151 | $invalidCharsNoSpace = [RegEx]::Escape( -join $arrInvalidChars ) 152 | 153 | #Check that the Replacement does not have invalid characters itself 154 | if ( $RemoveSpace ) { 155 | if ( $Replacement -match "[$invalidCharsWithSpace]" ) { 156 | Write-Error "The replacement string also contains invalid filename characters." 157 | exit 158 | } 159 | } 160 | else { 161 | if ( $Replacement -match "[$invalidCharsNoSpace]" ) { 162 | Write-Error "The replacement string also contains invalid filename characters." 163 | exit 164 | } 165 | } 166 | 167 | Function Remove-Chars($String) { 168 | #Test if any charcters should just be removed first instead of replaced. 169 | if ($RemoveOnly) { 170 | $String = Remove-ExemptCharsFromReplacement -String $String 171 | } 172 | 173 | #Replace the invalid characters with a blank string(removal) or the replacement value 174 | #Perform replacement based on whether spaces are desired or not 175 | if ($RemoveSpace) { 176 | [RegEx]::Replace($String, "[$invalidCharsWithSpace]", $Replacement) 177 | } 178 | else { 179 | [RegEx]::Replace($String, "[$invalidCharsNoSpace]", $Replacement) 180 | } 181 | } 182 | 183 | Function Remove-ExemptCharsFromReplacement($String) { 184 | #Remove the characters in RemoveOnly first before returning to the potential replacement 185 | 186 | #Test that the entries are invalid filename characters, and are able to be converted to chars 187 | $RemoveOnly = [RegEx]::Escape( 188 | -join $( 189 | ForEach ( $entry in $RemoveOnly ) { 190 | #Try to cast to an int in case a valid integer as a string is passed. 191 | try { 192 | $entry = [int]$entry 193 | } 194 | catch { 195 | #Silently ignore if it fails. 196 | } 197 | 198 | try { 199 | $char = [char]$entry 200 | } 201 | catch { 202 | Write-Error "The entry `"$entry`" in RemoveOnly cannot be converted to a type of System.Char. Make sure the entry is either an integer or a one character string." 203 | exit 204 | } 205 | 206 | if ( ( $arrInvalidChars -contains $char ) -or ( $char -eq [char]32 ) ) { 207 | #Honor the RemoveSpace parameter 208 | if ( ( !$RemoveSpace ) -and ( $char -eq [char]32 ) ) { 209 | Write-Warning "The entry `"$char`" in RemoveOnly is a valid filename character, and does not need to be removed. This entry will be ignored." 210 | } 211 | else { $char } 212 | } 213 | else { 214 | Write-Warning "The entry `"$char`" in RemoveOnly is a valid filename character, and does not need to be removed. This entry will be ignored." 215 | } 216 | } 217 | ) 218 | ) 219 | 220 | #Remove the exempt characters first before sending back 221 | [RegEx]::Replace( $String, "[$RemoveOnly]", '') 222 | } 223 | } 224 | 225 | Process { 226 | ForEach ( $n in $Name ) { 227 | #Check if the string matches a valid path 228 | if ( $n -match '(?^[a-zA-z]:\\|^\\\\)(?(?:[^\\]+\\)+)(?[^\\]+)$' ) { 229 | #Split the path into separate directories 230 | $path = $Matches.path -split '\\' 231 | 232 | #This will remove any empty elements after the split, eg. double slashes "\\" 233 | $path = $path | Where-Object { $_ } 234 | #Add the filename to the array 235 | $path += $Matches.file 236 | 237 | #Send each part of the path, except the start, to the removal function 238 | $cleanPaths = foreach ($p in $path) { 239 | Remove-Chars -String $p 240 | } 241 | #Remove any blank elements left after removal. 242 | $cleanPaths = $cleanPaths | Where-Object { $_ } 243 | 244 | #Combine the path together again 245 | $Matches.start + ( $cleanPaths -join '\' ) 246 | } 247 | else { 248 | #String is not a path, so send immediately to the removal function 249 | Remove-Chars -String $n 250 | } 251 | } 252 | } 253 | } -------------------------------------------------------------------------------- /Working.GroupCreation.ps1: -------------------------------------------------------------------------------- 1 | #Creating a table with sites creation needed: 2 | $GroupsToCreate = $allnbsites | Where-Object { $_.name -in $MissingGroups.DefinedEng } | Select-Object url, name, status, region, facility, physical_address, latitude, longitude 3 | 4 | $GroupsToCreate = Import-Csv -Path .\GroupsToCreate.csv 5 | 6 | ForEach ($gtc in $groupsToCreate) { 7 | $BMR_Eng_Number = $gtc.Name 8 | $groupName = $gtc.facility 9 | $longitude = $gtc.Longitude 10 | $latitude = $gtc.Latitude 11 | 12 | #creating the group: 13 | Invoke-SwisVerb -SwisConnection $SwisConnection -EntityName "Orion.Container" -Verb "CreateContainer" -Arguments @( 14 | # group name 15 | $groupname, 16 | # owner, must be 'Core' 17 | "Core", 18 | # refresh frequency 19 | 60, 20 | # Status rollup mode: 0 = Mixed status shows warning, 1 = Show worst status, 2 = Show best status 21 | 0, 22 | # group description 23 | "Created by Provisioning Script (PL) v1.0", 24 | # polling enabled/disabled = true/false (in lowercase) 25 | $true, 26 | ( [xml]"" ).DocumentElement 27 | ) 28 | 29 | $NewGroupQuery = @" 30 | SELECT [gcp].ContainerID 31 | , [g].Name 32 | , [g].LastChanged 33 | , [gcp].BMR_Eng_Number 34 | , [gcp].c 35 | , [gcp].Longitude 36 | , [gcp].Latitude 37 | , [gcp].LMRN_Description 38 | , [gcp].LMRN_Zone 39 | , [g].URI 40 | , CASE 41 | WHEN [g].URI IS NOT NULL THEN CONCAT([g].URI,'/CustomProperties') 42 | ELSE NULL 43 | END AS CP_URI 44 | FROM Orion.GroupCustomProperties AS [gcp] 45 | LEFT JOIN Orion.Groups AS [g] 46 | ON [gcp].ContainerID = [g].ContainerID 47 | WHERE [g].Name = '$groupname' 48 | "@ 49 | #Getting newly group information: 50 | $groupData = Get-SwisData $SwisConnection -Query $NewGroupQuery 51 | 52 | #Populating custom properties of the new group: 53 | $CP = @{ 54 | "BMR_Eng_Number" = $BMR_Eng_Number 55 | "Longitude" = $longitude 56 | "Latitude" = $latitude 57 | } 58 | Set-SwisObject -SwisConnection $SwisConnection -Uri $groupData.CP_URI -Properties $CP 59 | 60 | # Creating dynamic to populate group: 61 | $members = @( 62 | @{ Name = "BMR_Eng_Number is $BMR_Eng_Number"; Definition = "filter:/Orion.Nodes[CustomProperties.BMR_Eng_Number=$BMR_Eng_Number]" } 63 | ) 64 | Invoke-SwisVerb $SwisConnection -EntityName "Orion.Container" -Verb "AddDefinitions" -Arguments @( 65 | # group ID 66 | $groupData.ContainerID, 67 | # group member to add 68 | ([xml]@( 69 | "", 70 | [string]( $members | ForEach-Object { 71 | "$( $_.Name )$( $_.Definition )" 72 | } 73 | ), 74 | "" 75 | )).DocumentElement 76 | ) | Out-Null 77 | } -------------------------------------------------------------------------------- /Working.GroupCreation.ps1.bak: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmsigma/SwqlQueries/97b48cdf3e2a2165b4426ef24a4e011ef2220a3c/Working.GroupCreation.ps1.bak -------------------------------------------------------------------------------- /func_ModernDashboards.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Module @{ ModuleName = 'SwisPowerShell'; ModuleVersion = '3.0.309' } 2 | 3 | <# 4 | .Synopsis 5 | Export Modern Dashboards from SolarWinds Orion system 6 | .DESCRIPTION 7 | Connects to SolarWinds Information Service, extracts the JSON content of a Modern Dashboard and exports it to the file system 8 | .EXAMPLE 9 | $SwisConnection = Connect-Swis -Hostname "192.168.11.165" -Username "admin" -Password "MyComplexPassword" 10 | PS C:\> Set-Location -Path "C:\Exports" 11 | PS C:\Exports> Export-ModernDashboard -SwisConnection $SwisConnection 12 | 13 | This exports all of the Modern Dashboards to the 'C:\Exports' folder 14 | .EXAMPLE 15 | $SwisConnection = Connect-Swis -Hostname "192.168.11.165" -Username "admin" -Password "MyComplexPassword" 16 | PS C:\> Export-ModernDashboard -SwisConnection $SwisConnection -DashboardId 9 -OutputFolder "D:\OrionServer\Modern Dashboards\" 17 | 18 | This exports the Modern Dashboard with ID 9 to the 'D:\OrionServer\Modern Dashboards\' folder 19 | .EXAMPLE 20 | $SwisConnection = Connect-Swis -Hostname "192.168.11.165" -Username "admin" -Password "MyComplexPassword" 21 | PS C:\> Export-ModernDashboard -SwisConnection $SwisConnection -DashboardId 9 -IncludeId 22 | 23 | This exports the Modern Dashboard with ID 9 to the current folder with the naming format "9_.json" 24 | .NOTES 25 | Author: Kevin M. Sparenberg (https://thwack.solarwinds.com/members/kmsigma) 26 | Version: 0.9 27 | Last Updated: 2021-06-28 28 | Validated: Orion Platform 2020.2.5 29 | 30 | TBD List: 31 | * Add/incoporate the check for valid/invalid file names from Utilities 32 | * -PassThru [switch] 33 | Mimic a -PassThru parameter that just returns the JSON data to the pipeline 34 | -AsPsObject [switch] (child of -PassThru) 35 | Allows for the raw Json to be returned as a PowerShell object 36 | #> 37 | function Export-ModernDashboard { 38 | [CmdletBinding( 39 | DefaultParameterSetName = 'Normal', 40 | SupportsShouldProcess = $true, 41 | PositionalBinding = $false, 42 | HelpUri = 'https://documentation.solarwinds.com/en/success_center/orionplatform/content/core-fusion-dashboard-import-export.htm', 43 | ConfirmImpact = 'Medium')] 44 | [Alias()] 45 | [OutputType([String])] 46 | Param 47 | ( 48 | # The connection to the SolarWinds Information Service 49 | [Parameter( 50 | Mandatory = $true, 51 | ValueFromPipeline = $true, 52 | ValueFromPipelineByPropertyName = $true, 53 | ValueFromRemainingArguments = $false, 54 | Position = 0, 55 | ParameterSetName = 'Normal')] 56 | [ValidateNotNull()] 57 | [ValidateNotNullOrEmpty()] 58 | [Alias("Swis")] 59 | [SolarWinds.InformationService.Contract2.InfoServiceProxy]$SwisConnection, 60 | 61 | # The dashboard Id we'll export 62 | [Parameter( 63 | ValueFromPipeline = $true, 64 | ValueFromPipelineByPropertyName = $true, 65 | Position = 1, 66 | ParameterSetName = 'Normal')] 67 | [AllowNull()] 68 | [int32[]]$DashboardId, 69 | 70 | # Specifies the path to the output file. 71 | [Parameter(ParameterSetName = 'Normal')] 72 | [AllowNull()] 73 | [string]$OutputFolder = ( Get-Location ), 74 | 75 | # Should we include system Dashboards? 76 | [Parameter(ParameterSetName = 'Normal')] 77 | [AllowNull()] 78 | [switch]$IncludeSystem, 79 | 80 | # Should we include the Dashboard ID number in the name. 81 | [Parameter(ParameterSetName = 'Normal')] 82 | [AllowNull()] 83 | [switch]$IncludeId, 84 | 85 | # Omits white space and indented formatting in the output string. 86 | [Parameter(ParameterSetName = 'Normal')] 87 | [AllowNull()] 88 | [switch]$Compress, 89 | 90 | # Overrides the read-only attribute and overwrites an existing read-only file. The Force parameter does not override security restrictions. 91 | [Parameter(ParameterSetName = 'Normal')] 92 | [AllowNull()] 93 | [switch]$Force 94 | 95 | ) 96 | 97 | Begin { 98 | # if no dashboard ids are provided, assume we export the all, so get the list 99 | if ( ( -not $DashboardId ) -and ( $IncludeSystem ) ) { 100 | Write-Verbose -Message "EXPORT ALL: No DashboardIds Provided - exporting all (including system dashboards)" 101 | $Swql = "SELECT DashboardID FROM Orion.Dashboards.Instances WHERE ParentID IS NULL" 102 | $DashboardId = Get-SwisData -SwisConnection $SwisConnection -Query $Swql 103 | } elseif ( -not $DashboardId ) { 104 | Write-Verbose -Message "EXPORT ALL: No DashboardIds Provided - exporting all (excluding system dashboards)" 105 | $Swql = "SELECT DashboardID FROM Orion.Dashboards.Instances WHERE ParentID IS NULL AND IsSystem = 'FALSE'" 106 | $DashboardId = Get-SwisData -SwisConnection $SwisConnection -Query $Swql 107 | } 108 | 109 | # How deep does the Json go? From initial testing it looks like 25 is sufficient, this gives flexibility 110 | $JsonDepth = 25 111 | } 112 | Process { 113 | ForEach ( $d in $DashboardId ) { 114 | $DashboardText = Invoke-SwisVerb -SwisConnection $SwisConnection -EntityName Orion.Dashboards.Instances -Verb Export -Arguments $d 115 | 116 | # The name is stored within the Json file, so we need to load it as Json and interpret it. 117 | $DashboardObject = $DashboardText.'#text' | ConvertFrom-Json 118 | $DashboardName = $DashboardObject.dashboards.name 119 | if ( $DashboardObject.dashboards.unique_key -notmatch '[a-f,0-9]{8}-[a-f,0-9]{4}-[a-f,0-9]{4}-[a-f,0-9]{4}-[a-f,0-9]{12}' ) { 120 | # This is a system dashboard, so add "SYSTEM" to the name 121 | $DashboardName = "SYSTEM_$( $DashboardName )" 122 | } 123 | 124 | $ExportFileName = "$( Remove-InvalidFileNameChars -Name $DashboardName -Replacement "-" ).json" 125 | if ( $IncludeId ) { 126 | $ExportFileName = "$( $d )_$ExportFileName" 127 | } 128 | 129 | $ExportFilePath = Join-Path -Path $OutputFolder -ChildPath $ExportFileName 130 | 131 | # Check to see if the export file already exists and we are not forcing overwrite 132 | if ( ( -not ( Test-Path -Path $ExportFilePath -ErrorAction SilentlyContinue ) ) -or ( $Force ) ) { 133 | # Ask if we want to export 134 | if ( $pscmdlet.ShouldProcess("to '$OutputFolder'", "Export '$DashboardName'") ) { 135 | # Actually do the export 136 | Write-Verbose -Message "Exporting '$DashboardName'" 137 | $DashboardObject | ConvertTo-Json -Depth $JsonDepth -Compress:$Compress | Out-File -FilePath $ExportFilePath -Force:$Force 138 | } 139 | } 140 | else { 141 | Write-Warning -Message "Skipping export of '$DashboardName' because '$ExportFilePath' already exists. If you wish to overwrite, use the '-Force' parameter." 142 | } 143 | } 144 | } 145 | End { 146 | # nothing to do here 147 | } 148 | } 149 | 150 | <# 151 | .Synopsis 152 | Import Modern Dashboards to a SolarWinds Orion system 153 | .DESCRIPTION 154 | Opens files in the file system and imports them as custom Modern Dashboards into an Orion Server. 155 | .EXAMPLE 156 | $SwisConnection = Connect-Swis -Hostname "192.168.11.165" -Username "admin" -Password "MyComplexPassword" 157 | PS C:\> Set-Location -Path "C:\Exports" 158 | PS C:\Exports> Import-ModernDashboard -SwisConnection $SwisConnection 159 | 160 | This imports all of the Modern Dashboards files in the 'C:\Exports' folder to the server running on 192.168.11.165 161 | .EXAMPLE 162 | $SwisConnection = Connect-Swis -Hostname "192.168.11.165" -Username "admin" -Password "MyComplexPassword" 163 | PS C:\> Import-ModernDashboard -SwisConnection $SwisConnection -Path "C:\Imports\KevinsDashboard.json" 164 | 165 | This imports a single dashboard from the "C:\Imports\KevinsDashboard.json" file 166 | .EXAMPLE 167 | $SwisConnection = Connect-Swis -Hostname "192.168.11.165" -Username "admin" -Password "MyComplexPassword" 168 | PS C:\> Import-ModernDashboard -SwisConnection $SwisConnection -Path "C:\Imports\KevinsDashboard.json" 169 | 170 | PS C:\> Import-ModernDashboard -SwisConnection $SwisConnection -Path "C:\Imports\KevinsDashboard.json" 171 | This will fail with an error because there already exists this import. To forcibly import (descructive): 172 | PS C:\> Import-ModernDashboard -SwisConnection $SwisConnection -Path "C:\Imports\KevinsDashboard.json" -Force 173 | .NOTES 174 | Author: Kevin M. Sparenberg (https://thwack.solarwinds.com/members/kmsigma) 175 | Version: 0.9 176 | Last Updated: 2021-06-28 177 | Validated: Orion Platform 2020.2.5 178 | #> 179 | function Import-ModernDashboard { 180 | [CmdletBinding(DefaultParameterSetName = 'Normal', 181 | SupportsShouldProcess = $true, 182 | PositionalBinding = $false, 183 | HelpUri = 'https://documentation.solarwinds.com/en/success_center/orionplatform/content/core-fusion-dashboard-import-export.htm', 184 | ConfirmImpact = 'High')] 185 | [Alias()] 186 | [OutputType([String])] 187 | Param 188 | ( 189 | # The connection to the SolarWinds Information Service 190 | [Parameter(Mandatory = $true, 191 | ValueFromPipeline = $true, 192 | ValueFromPipelineByPropertyName = $true, 193 | ValueFromRemainingArguments = $false, 194 | Position = 0, 195 | ParameterSetName = 'Normal')] 196 | [ValidateNotNull()] 197 | [ValidateNotNullOrEmpty()] 198 | [Alias("Swis")] 199 | [SolarWinds.InformationService.Contract2.InfoServiceProxy]$SwisConnection, 200 | 201 | # If a pre-existing dashboard name matches, use a different name 202 | [Parameter(Position = 1, 203 | ParameterSetName = 'Normal')] 204 | [string[]]$Path = ( Get-Location ), 205 | 206 | # If a pre-existing dashboard name matches, overwrite it 207 | [Parameter(ParameterSetName = 'Normal')] 208 | [AllowNull()] 209 | [switch]$Force 210 | 211 | ) 212 | 213 | Begin { 214 | # How deep does the Json go? From initial testing it looks like 25 is sufficient, this gives flexibility 215 | $JsonDepth = 25 216 | 217 | # Need a list of existing Modern Dashboards 218 | $Swql = "SELECT DisplayName, UniqueKey FROM Orion.Dashboards.Instances WHERE ParentID IS NULL" 219 | $Dashboards = Get-SwisData -SwisConnection $SwisConnection -Query $Swql 220 | } 221 | Process { 222 | $FileList = @() 223 | ForEach ( $P in $Path ) { 224 | $P = Get-Item -Path $P 225 | if ( $P.PSIsContainer ) { 226 | Write-Verbose -Message "PATH DETECTION: Directory Detected" 227 | $FileList += Get-ChildItem -Path $P 228 | } else { 229 | Write-Verbose -Message "PATH DETECTION: Single File" 230 | $FileList += $P 231 | } 232 | } 233 | 234 | ForEach ( $File in $FileList ) { 235 | $TemplateObject = Get-Content -Path $File | ConvertFrom-Json -Depth $JsonDepth 236 | $DashboardExists = ( $TemplateObject.dashboards.Name -in $Dashboards.DisplayName ) -or ( $TemplateObject.dashboards.unique_key -in $Dashboards.UniqueKey ) 237 | 238 | if ( $pscmdlet.ShouldProcess("Orion Server: [$( $SwisConnection.Channel.Via.Host )]", "Import Dashboard [$( $TemplateObject.dashboards.name )]") ) { 239 | if ( ( -not ( $DashboardExists ) ) -or ( $Force ) ) { 240 | Write-Verbose -Message "Importing Dashboard '$( $TemplateObject.dashboards.name )' to Orion Server: [$( $SwisConnection.Channel.Via.Host )]" 241 | Invoke-SwisVerb -SwisConnection $SwisConnection -EntityName "Orion.Dashboards.Instances" -Verb "Import" -Arguments ( $TemplateObject | ConvertTo-Json -Depth $JsonDepth -Compress ) | Out-Null 242 | } else { 243 | Write-Error -Message "SKIPPED: Dashboard with name '$( $TemplateObject.dashboards.Name )' or key [$( $TemplateObject.dashboards.unique_key )] already exists." -RecommendedAction "Use '-Force' to forcibly import (destructive)." 244 | } 245 | } 246 | } 247 | 248 | } 249 | End { 250 | # nothing to do here 251 | } 252 | } 253 | 254 | 255 | <# 256 | .SYNOPSIS 257 | Removes characters from a string that are not valid in Windows file names. 258 | 259 | .DESCRIPTION 260 | Remove-InvalidFileNameChars accepts a string and removes characters that are invalid in Windows file names. It then outputs the cleaned string. By default the space character is ignored, but can be included using the RemoveSpace parameter. 261 | 262 | The Replacement parameter will replace the invalid characters with the specified string. Its companion RemoveOnly will exempt given invalid characters from being replaced, and will simply be removed. Charcters in this list can be given as a string or their decimal or hexadecimal representation. 263 | 264 | The Name parameter can also clean file paths. If the string begins with "\\" or a drive like "C:\", it will then treat the string as a file path and clean the strings between "\". This has the side effect of removing the ability to actually remove the "\" character from strings since it will then be considered a divider. 265 | .PARAMETER Name 266 | Specifies the file name to strip of invalid characters. 267 | 268 | .PARAMETER Replacement 269 | Specifies the string to use as a replacement for the invalid characters. 270 | 271 | .PARAMETER RemoveOnly 272 | Specifes the list of invalid characters to remove from the string instead of being replaced by the Replacement parameter value. This may be given as one character strings, or their decimal or hexidecimal values. 273 | 274 | .PARAMETER RemoveSpace 275 | The RemoveSpace parameter will include the space character (U+0020) in the removal process. 276 | 277 | .INPUTS 278 | System.String 279 | Remove-InvalidFileNameChars accepts System.String objects in the pipeline. 280 | 281 | Remove-InvalidFileNameChars accepts System.String objects in a property Name from objects in the pipeline. 282 | 283 | .OUTPUTS 284 | System.String 285 | 286 | .EXAMPLE 287 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" 288 | Output: This name is an illegal filename.txt 289 | 290 | This command will strip the invalid characters from the string and output a clean string. 291 | .EXAMPLE 292 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" -RemoveSpace 293 | Output: Thisnameisanillegalfilename.txt 294 | 295 | This command will strip the invalid characters from the string and output a clean string, removing the space character (U+0020) as well. 296 | .EXAMPLE 297 | PS C:\> Remove-InvalidFileNameChars -Name '\\Path/:|?*<\With:*?>\:Illegal /Characters>?*.txt"' 298 | Output: \\Path\With\Illegal Characters.txt 299 | 300 | This command will strip the invalid characters from the path and output a valid path. Note: it would not be able to remove the "\" character. 301 | .EXAMPLE 302 | PS C:\> Remove-InvalidFileNameChars -Name '\\Path/:|?*<\With:*?>\:Illegal /Characters>?*.txt"' -RemoveSpace 303 | Output: \\Path\With\IllegalCharacters.txt 304 | 305 | This command will strip the invalid characters from the path and output a valid path, also removing the space character (U+0020) as well. Note: it would not be able to remove the "\" character. 306 | .EXAMPLE 307 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" -Replacement + 308 | Output: +This +name +is+ an +illegal +filename+.txt 309 | 310 | This command will strip the invalid characters from the string, replacing them with a "+", and outputting the result string. 311 | .EXAMPLE 312 | PS C:\> Remove-InvalidFileNameChars -Name ".txt" -Replacemet + -RemoveOnly "*", 58, 0x3f 313 | Output: +This +name +is an illegal filename+.txt 314 | 315 | This command will strip the invalid characters from the string, replacing them with a "+", except the "*", the charcter with a decimal value of 58 (:), and the character with a hexidecimal value of 0x3f (?). These will simply be removed, and the resulting string output. 316 | .NOTES 317 | Author: Chris Carter 318 | Version: 1.5.1 319 | Last Updated: August 8, 2016 320 | 321 | .Link 322 | System.RegEx 323 | .Link 324 | about_Join 325 | .Link 326 | about_Operators 327 | #> 328 | 329 | #Requires -Version 2.0 330 | function Remove-InvalidFileNameChars { 331 | 332 | [CmdletBinding( 333 | DefaultParameterSetName = "Normal", 334 | HelpURI = 'https://gallery.technet.microsoft.com/scriptcenter/Remove-Invalid-Characters-39fa17b1' 335 | )] 336 | 337 | Param( 338 | [Parameter(Mandatory = $true, 339 | Position = 0, 340 | ValueFromPipeline = $true, 341 | ValueFromPipelineByPropertyName = $true, 342 | ParameterSetName = "Normal")] 343 | [Parameter(Mandatory = $true, 344 | Position = 0, 345 | ValueFromPipeline = $true, 346 | ValueFromPipelineByPropertyName = $true, 347 | ParameterSetName = "Replace")] 348 | [string[]]$Name, 349 | 350 | [Parameter(Mandatory = $true, 351 | Position = 1, 352 | ParameterSetName = "Replace")] 353 | [string]$Replacement, 354 | 355 | [Parameter(Position = 2, 356 | ParameterSetName = "Replace")] 357 | [Alias("RO")] 358 | [object[]]$RemoveOnly, 359 | 360 | [Parameter(ParameterSetName = "Normal")] 361 | [Parameter(ParameterSetName = "Replace")] 362 | [Alias("RS")] 363 | [switch]$RemoveSpace 364 | ) 365 | 366 | Begin { 367 | #Get an array of invalid characters 368 | $arrInvalidChars = [System.IO.Path]::GetInvalidFileNameChars() 369 | 370 | #Cast into a string. This will include the space character 371 | $invalidCharsWithSpace = [RegEx]::Escape( [string]$arrInvalidChars ) 372 | 373 | #Join into a string. This will not include the space character 374 | $invalidCharsNoSpace = [RegEx]::Escape( -join $arrInvalidChars ) 375 | 376 | #Check that the Replacement does not have invalid characters itself 377 | if ( $RemoveSpace ) { 378 | if ( $Replacement -match "[$invalidCharsWithSpace]" ) { 379 | Write-Error "The replacement string also contains invalid filename characters." 380 | exit 381 | } 382 | } 383 | else { 384 | if ( $Replacement -match "[$invalidCharsNoSpace]" ) { 385 | Write-Error "The replacement string also contains invalid filename characters." 386 | exit 387 | } 388 | } 389 | 390 | Function Remove-Chars($String) { 391 | #Test if any charcters should just be removed first instead of replaced. 392 | if ($RemoveOnly) { 393 | $String = Remove-ExemptCharsFromReplacement -String $String 394 | } 395 | 396 | #Replace the invalid characters with a blank string(removal) or the replacement value 397 | #Perform replacement based on whether spaces are desired or not 398 | if ($RemoveSpace) { 399 | [RegEx]::Replace($String, "[$invalidCharsWithSpace]", $Replacement) 400 | } 401 | else { 402 | [RegEx]::Replace($String, "[$invalidCharsNoSpace]", $Replacement) 403 | } 404 | } 405 | 406 | Function Remove-ExemptCharsFromReplacement($String) { 407 | #Remove the characters in RemoveOnly first before returning to the potential replacement 408 | 409 | #Test that the entries are invalid filename characters, and are able to be converted to chars 410 | $RemoveOnly = [RegEx]::Escape( 411 | -join $( 412 | ForEach ( $entry in $RemoveOnly ) { 413 | #Try to cast to an int in case a valid integer as a string is passed. 414 | try { 415 | $entry = [int]$entry 416 | } 417 | catch { 418 | #Silently ignore if it fails. 419 | } 420 | 421 | try { 422 | $char = [char]$entry 423 | } 424 | catch { 425 | Write-Error "The entry `"$entry`" in RemoveOnly cannot be converted to a type of System.Char. Make sure the entry is either an integer or a one character string." 426 | exit 427 | } 428 | 429 | if ( ( $arrInvalidChars -contains $char ) -or ( $char -eq [char]32 ) ) { 430 | #Honor the RemoveSpace parameter 431 | if ( ( !$RemoveSpace ) -and ( $char -eq [char]32 ) ) { 432 | Write-Warning "The entry `"$char`" in RemoveOnly is a valid filename character, and does not need to be removed. This entry will be ignored." 433 | } 434 | else { $char } 435 | } 436 | else { 437 | Write-Warning "The entry `"$char`" in RemoveOnly is a valid filename character, and does not need to be removed. This entry will be ignored." 438 | } 439 | } 440 | ) 441 | ) 442 | 443 | #Remove the exempt characters first before sending back 444 | [RegEx]::Replace( $String, "[$RemoveOnly]", '') 445 | } 446 | } 447 | 448 | Process { 449 | ForEach ( $n in $Name ) { 450 | #Check if the string matches a valid path 451 | if ( $n -match '(?^[a-zA-z]:\\|^\\\\)(?(?:[^\\]+\\)+)(?[^\\]+)$' ) { 452 | #Split the path into separate directories 453 | $path = $Matches.path -split '\\' 454 | 455 | #This will remove any empty elements after the split, eg. double slashes "\\" 456 | $path = $path | Where-Object { $_ } 457 | #Add the filename to the array 458 | $path += $Matches.file 459 | 460 | #Send each part of the path, except the start, to the removal function 461 | $cleanPaths = foreach ($p in $path) { 462 | Remove-Chars -String $p 463 | } 464 | #Remove any blank elements left after removal. 465 | $cleanPaths = $cleanPaths | Where-Object { $_ } 466 | 467 | #Combine the path together again 468 | $Matches.start + ( $cleanPaths -join '\' ) 469 | } 470 | else { 471 | #String is not a path, so send immediately to the removal function 472 | Remove-Chars -String $n 473 | } 474 | } 475 | } 476 | } -------------------------------------------------------------------------------- /func_PingTrace.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Function that runs a 'PowerShell' ping and trace route - optionally adds it to an alert note 4 | .NOTES 5 | To help with https://thwack.solarwinds.com/product-forums/the-orion-platform/f/alert-lab/91386/embedding-output-into-an-alert-email-action 6 | 7 | #> 8 | function Test-PingTrace { 9 | [CmdletBinding( 10 | HelpUri = 'https://thwack.solarwinds.com/product-forums/the-orion-platform/f/alert-lab/91386/embedding-output-into-an-alert-email-action/', 11 | SupportsShouldProcess = $true, 12 | ConfirmImpact = 'Low')] 13 | [Alias()] 14 | [OutputType([String])] 15 | Param 16 | ( 17 | # The IP Address of the thing on which to operate 18 | [Parameter(Mandatory = $true)] 19 | [Alias("IP")] 20 | [string]$IPAddress, 21 | 22 | # Number of times to try and ping (default to 4) 23 | [int]$NumPings = 4, 24 | 25 | # Number of times to hop for a trace (default to 15) 26 | [int]$NumHops = 15 27 | ) 28 | 29 | Begin { 30 | # Nothing to see here 31 | } 32 | Process { 33 | if ( $pscmdlet.ShouldProcess("$IPAddress", "Ping the thing") ) { 34 | 35 | Write-Verbose -Message "Sending $NumPings to '$IPAddress'" 36 | # Using Test-Connection adds a whole bunch of other stuff we don't need, so get the basics 37 | $PingResults = Test-Connection -ComputerName $IPAddress -Count $NumPings -ErrorAction SilentlyContinue | Select-Object -Property @{ Name = "Source"; Expression = { $_.PSComputerName } }, Address, ReplySize, ResponseTime 38 | 39 | Write-Verbose -Message "Running a trace to '$IPAddress' with up to $NumHops hops" 40 | # Tracing using Test-NetConnection adds a whole bunch of other stuff we don't need, so get the basics 41 | $TraceRtResults = Test-NetConnection -ComputerName $IPAddress -TraceRoute -Hops $NumHops -ErrorAction SilentlyContinue | Select-Object SourceAddress, RemoteAddress, @{ Name = "RTT"; Expression = { $_.PingReplyDetails.RoundtripTime } }, TraceRoute 42 | 43 | # Start with an empty Note 44 | $Note = "" 45 | if ( $PingResults ) { 46 | # Add the ping success information to the Note 47 | $Note += @" 48 | Ping Results: 49 | ----------------------------------------------------- 50 | Source Address: $( $env:COMPUTERNAME ) 51 | Remote Address: $( $PingResults[0].Address ) 52 | Number of Pings: $NumPings 53 | Avg Response Time (ms): $( $PingResults | Measure-Object -Property ResponseTime -Average | Select-Object -ExpandProperty Average ) 54 | `n 55 | "@ 56 | } 57 | else { 58 | # Add the ping failure information to the Note 59 | $Note += @" 60 | Ping Results: 61 | ----------------------------------------------------- 62 | Source Address: $( $env:COMPUTERNAME ) 63 | Remote Address: $IPAddress 64 | Number of Pings: $NumPings 65 | Avg Response Time (ms): N/A 66 | ***** PING FAILED ***** 67 | `n 68 | "@ 69 | } 70 | 71 | if ( $TraceRtResults ) { 72 | # Add the tracert success information to the Note 73 | $Note += @" 74 | Trace Route Results: 75 | ----------------------------------------------------- 76 | Source Address: $( $env:COMPUTERNAME ) 77 | Remote Address: $IPAddress 78 | Round Trip Time (ms): $( $TraceRtResults.RTT ) 79 | Hops: 80 | $( $TraceRtResults.TraceRoute | ForEach-Object { "`t$( $_ )`n" } ) 81 | `n 82 | "@ 83 | } 84 | else { 85 | # Add the tracert success information to the Note 86 | $Note += @" 87 | Trace Route Results: 88 | ----------------------------------------------------- 89 | Source Address: $( $env:COMPUTERNAME ) 90 | Remote Address: $IPAddress 91 | Round Trip Time (ms): N/A 92 | Hops: 93 | N/A 94 | ***** TRACE FAILED ***** 95 | `n 96 | "@ 97 | 98 | } 99 | 100 | # We've finished building the note, so let's send it back outside the function 101 | $Note 102 | 103 | } 104 | } 105 | 106 | End { 107 | # Nothing to do here 108 | } 109 | } --------------------------------------------------------------------------------