├── .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 |
107 |
108 |
109 |
110 |
111 |
112 | ${N=SwisEntity;M=Caption} is ${N=SwisEntity;M=Status} |
113 |
114 |
115 | Alert Name: ${N=Alerting;M=AlertName} / Trigger Time:
116 | ${N=Alerting;M=AlertTriggerTime;F=DateTime} |
117 |
118 |
119 |
120 |
121 |
122 |
142 | |
143 |
144 |
145 | |
146 |
147 |
148 | Alert Details: ${N=Alerting;M=AlertDescription} |
149 |
150 |
151 |
152 |
156 | |
157 |
158 |
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 3 that this is a text field. Personally, I would have gone with a boolean (yes/no, true/false), but I'll live.
66 | if ( -not ( Get-SwisData -SwisConnection $TargetSwisConnection -Query "SELECT Field FROM Orion.CustomProperty WHERE Table='Nodes' AND Field='ImportedByAPI'" ) )
67 | {
68 | Invoke-SwisVerb -SwisConnection $TargetSwisConnection -EntityName "Orion.NodesCustomProperties" -Verb "CreateCustomProperty" -Arguments @( "ImportedByAPI", "created from PowerShell", "System.String", 100, "", "", "", "", "", "" )
69 | }
70 |
71 | #region List of the Queries we'll be using
72 | # I almost always do my queries in a multi-line (here-string) format because it's easier for me to read
73 | $SwqlEngines = @"
74 | SELECT EngineID
75 | , ServerName
76 | , IP
77 | , ServerType
78 | FROM Orion.Engines
79 | ORDER BY EngineID
80 | "@
81 | $SwqlHasNpm = @"
82 | SELECT Name
83 | FROM Orion.InstalledModule
84 | WHERE Name = 'NPM'
85 | AND IsExpired = 'False'
86 | "@
87 | $SwqlHasNcm = @"
88 | SELECT Name
89 | FROM Orion.InstalledModule
90 | WHERE Name = 'NCM'
91 | AND IsExpired = 'False'
92 | "@
93 | $SwqlNodeUriByIp = @"
94 | SELECT Uri
95 | FROM Orion.Nodes
96 | WHERE IPAddress = @IP
97 | "@
98 |
99 | $SwqlNodes = @"
100 | SELECT Uri
101 | , IPAddress
102 | , Caption
103 | , NodeID
104 | FROM Orion.Nodes
105 | WHERE ObjectSubType = 'SNMP'
106 | ORDER BY Caption
107 | "@
108 | $SwqlPollers = @"
109 | SELECT PollerType
110 | FROM Orion.Pollers
111 | WHERE NetObject = @NetObject
112 | "@
113 |
114 | $SwqlInterfaceUrisByNode = @"
115 | SELECT Nodes.Interfaces.Uri
116 | FROM Orion.Nodes
117 | WHERE NodeID = @NodeID
118 | "@
119 |
120 | $SwqlVolumeUrisByNodeID = @"
121 | SELECT Nodes.Volumes.Uri
122 | FROM Orion.Nodes
123 | WHERE NodeID = @NodeID
124 | "@
125 | #endregion List of the Queries we'll be using
126 |
127 | # Check whether both the source and target have NPM installed
128 | # If the system supports interfaces, then we can safely assume that NPM is installed
129 | $SourceHasNpm = ( -not ( Get-SwisData -SwisConnection $SourceSwisConnection -Query $SwqlHasNpm ) )
130 | $TargetHasNpm = ( -not ( Get-SwisData -SwisConnection $TargetSwisConnection -Query $SwqlHasNpm ) )
131 |
132 | # Check whether the target has NCM installed
133 | # if the query returns anything, then NCM is installed
134 | $TargetHasNCM = ( -not ( Get-SwisData -SwisConnection $TargetSwisConnection -Query $SwqlHasNcm ) )
135 |
136 | # Get the complete list of Nodes from the source system
137 | # You can add a WHERE clause to this query if you only want to copy certain nodes
138 | $SourceNodes = Get-SwisData -SwisConnection $SourceSwisConnection -Query $SwqlNodes
139 |
140 | $TargetEngines = Get-SwisData -SwisConnection $TargetSwisConnection -Query $SwqlEngines
141 | # check to see if we're dealing with multiple polling engines
142 | if ( $TargetEngines.Count -gt 1 )
143 | {
144 | # Take the cheater way out at the moment and select the first engine
145 | $TargetEngineID = $TargetEngines[0].EngineID
146 | Write-Host "We are going load the nodes on $( $TargetEngines[0].ServerName ) ($( $TargetEngines[0].IP )). If you want to move them, you can do this in Node Management" -ForegroundColor Yellow
147 | }
148 |
149 | ForEach ( $SourceNode in $SourceNodes )
150 | {
151 |
152 | # See if there is aleady a node on the target with the same IP address
153 | $TargetNodeUri = Get-SwisData -SwisConnection $TargetSwisConnection -Query $SwqlNodeUriByIp -Parameters @{ "IP" = $SourceNode.IPAddress }
154 | # Original script had "$target -eq $null", but "-eq $null" is redundant. You can just check for existence using "-not"
155 | if ( $TargetNodeUri )
156 | {
157 | Write-Host "Skipping $( $SourceNode.Caption) ($( $SourceNode.IPAddress )) because it is already managed by $TargetOrionServer"
158 | continue
159 | }
160 |
161 | # Fetch all properties of the source node
162 | $SourceNodeProps = Get-SwisObject -SwisConnection $SourceSwisConnection -Uri $SourceNode.Uri
163 |
164 | <# Removed this section because we are already checking for SNMP only in the $SwqlNodes query above
165 | # Skip WMI nodes - this script does not support copying Windows credentials
166 | if ( $SourceNodeProps.ObjectSubType -eq "WMI" ) {
167 | Write-Host "Skipping" $SourceNode.Caption "(" $SourceNode.IPAddress ") because it uses WMI."
168 | continue
169 | }
170 | #>
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 | }
--------------------------------------------------------------------------------