Last active
February 26, 2024 17:16
-
-
Save JustinGrote/7bca185f79d92922531ca06fcaaa5fbb to your computer and use it in GitHub Desktop.
An extension of the PowerAlto module to enable running "ssh" commands via RPC-XML thru panorama to managed devices without having direct line of sight to the managed firewalls. A sample report dump of the Arp/Route/Interface/VPN is included
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#requires -module PowerAlto, ImportExcel | |
$ErrorActionPreference = 'Stop' | |
if (-not $MacVendorCache) { | |
Write-Warning 'MacVendorCache not found, downloading from maclookup.app. This is common on the first run.' | |
$SCRIPT:MacVendorCache = @{} | |
foreach ($entry in $(ConvertFrom-Csv (Invoke-RestMethod 'https://maclookup.app/downloads/csv-database/get-db'))) { | |
$MacVendorCache[$entry.'Mac Prefix'] = $entry.'Vendor Name' | |
} | |
} | |
function ConvertTo-PAXML { | |
param ( | |
[string]$Command | |
) | |
$xml = '' | |
$words = $Command.Split(' ') | |
#If the last word has a !, this is an indicator for an enumerator e.g. show arp !all | |
#Some commands want this to be done as an entry property, for those we use ? e.g. show arp ?ethernet1/8 | |
#Unknown how to determine which to use | |
if ($words[-1].StartsWith('?')) { | |
$xml = "<entry name='$($words[-1].TrimStart('?'))'/>" | |
$words = $words | Select-Object -SkipLast 1 | |
} | |
if ($words[-1].StartsWith('!')) { | |
$xml = $words[-1].TrimStart('!') | |
$words = $words | Select-Object -SkipLast 1 | |
} | |
[Array]::Reverse($words) | |
$words | ForEach-Object { | |
$xml = '<' + $_ + '>' + $xml + '</' + $_ + '>' | |
} | |
return $xml | |
} | |
function Invoke-PACommand([string]$Command, [PSTypeName('ManagedDevice')]$target, $Client = $GLOBAL:PaDeviceObject, [switch]$Raw) { | |
$xml = ConvertTo-PAXML -Command $Command | |
$query = @{ | |
type = 'op' | |
cmd = $Raw ? $xml : (ConvertTo-PAXML -Command $Command) | |
} | |
if ($target) { | |
$query.target = $target.serial | |
} | |
$query | Out-String | Write-Debug | |
$result = $Client.InvokeApiQuery($query) | |
if (-not $result.response) { throw 'Unexpected or null response from device' } | |
if ($result.response.status -ne 'success') { throw "Command failed with status $($result.response.status) and message $($result.response.result)" } | |
return $result.response.result | |
} | |
function Get-PAManagedDevice([switch]$Connected, $Client = $GLOBAL:PaDeviceObject) { | |
$command = $Connected ? 'show devices connected' : 'show devices all' | |
$result = Invoke-PACommand -Command $command -Client $Client | |
$result.devices.entry | |
| Select-Object @{N = 'device'; E = { $targetItem } }, hostname, name, serial, connected, ip-address, model, uptime, sw-version, app-version, av-version, device-dictionary-version, wildfire-version, thread-version, url-db | |
| ForEach-Object { | |
Add-Member -InputObject $_ -MemberType 'ScriptMethod' -Name 'ToString' -Value { $this.hostname } -Force | |
$_.pstypenames.Insert(0, 'ManagedDevice') | |
$_ | |
} | |
} | |
function Get-MacVendor([string]$MacAddress) { | |
$Mac = $MacAddress.Substring(0, 8) | |
$MacVendorCache[$Mac] ?? 'Unknown' | |
} | |
filter Get-PAArpTable([Parameter(ValueFromPipeline)][PSTypeName('ManagedDevice')]$target = (Get-PAManagedDevice -Connected), $Client = $GLOBAL:PaDeviceObject, [switch]$NoMacLookup) { | |
foreach ($targetItem in $target) { | |
$result = Invoke-PACommand -Command 'show arp ?all' -Client $Client -Target $targetItem | |
$result.entries.entry | |
| Where-Object { $_ } | |
| Select-Object @{N = 'device'; E = { $targetItem } }, interface, ip, mac, vendor, status, ttl | |
| ForEach-Object { | |
if (-not $_) { return } | |
$_.vendor = $_.mac -and -not $NoMacLookup ? $(Get-MacVendor $_.mac) : 'Unknown' | |
$_.pstypenames.Insert(0, 'ArpEntry') | |
return $_ | |
} | |
} | |
} | |
[Flags()] | |
enum routeType { | |
Unspecified = 0 | |
Active = 1 | |
Loose = 1 -shl 1 | |
Connect = 1 -shl 2 | |
Host = 1 -shl 3 | |
Internal = 1 -shl 4 | |
Rip = 1 -shl 5 | |
Ospf = 1 -shl 6 | |
Bgp = 1 -shl 7 | |
OspfIntraArea = 1 -shl 8 | |
OspfInterArea = 1 -shl 9 | |
OspfExt1 = 1 -shl 10 | |
OspfExt2 = 1 -shl 11 | |
Ecmp = 1 -shl 12 | |
Multicast = 1 -shl 13 | |
} | |
filter Get-PARouteTable([Parameter(ValueFromPipeline)][PSTypeName('ManagedDevice')]$target = (Get-PAManagedDevice -Connected), $Client = $GLOBAL:PaDeviceObject) { | |
foreach ($targetItem in $target) { | |
$result = Invoke-PACommand -Command 'show routing route' -Client $Client -Target $targetItem | |
$result.entry | |
| Where-Object { $_ } | |
| Select-Object @{N = 'device'; E = { $targetItem } }, destination, nexthop, interface, metric, flags, age, virtual-router, route-table | |
| ForEach-Object { | |
$_.flags = [routeType]($_.flags -split ' ' | Where-Object { $_ } | ForEach-Object { | |
$flag = switch ($PSItem) { | |
A { [routeType]::Active } | |
'?' { [routeType]::Loose } | |
C { [routeType]::Connect } | |
H { [routeType]::Host } | |
S { [routeType]::Static } | |
~ { [routeType]::Internal } | |
R { [routeType]::Rip } | |
O { [routeType]::Ospf } | |
B { [routeType]::Bgp } | |
Oi { [routeType]::Ospf_Intra_Area } | |
Oo { [routeType]::Ospf_Inter_Area } | |
O1 { [routeType]::Ospf_Ext1 } | |
O2 { [routeType]::Ospf_Ext2 } | |
E { [routeType]::Ecmp } | |
M { [routeType]::Multicast } | |
$null { [routeType]::None } | |
default { throw 'Unknown route type, this is a bug' } | |
} | |
$flag ??= [routeType]::Unspecified | |
$flag | |
}) | |
$_.pstypenames.Insert(0, 'RouteEntry') | |
$_ | |
} | |
} | |
} | |
filter Get-PAInterface([Parameter(ValueFromPipeline)][PSTypeName('ManagedDevice')]$target = (Get-PAManagedDevice -Connected), [string]$Name = 'all') { | |
foreach ($targetItem in $target) { | |
$result = Invoke-PACommand -target $targetItem -Command "show interface !$Name" | |
$hw = $result.hw.entry | Select-Object name, mac, state | |
$result.ifnet.entry | Select-Object @{N = 'device'; E = { $targetItem } }, | |
name, | |
id, | |
tag, | |
vsys, | |
zone, | |
fwd, | |
ip, | |
@{N = 'mac'; E = { ($hw | Where-Object name -EQ $PSItem.name)[0]?.mac } }, | |
@{N = 'state'; E = { ($hw | Where-Object name -EQ $PSItem.name)[0]?.state } } | |
| ForEach-Object { | |
$PSItem.pstypenames.Insert(0, 'Interface') | |
$PSItem | |
} | |
} | |
} | |
filter Get-PAVpnSession([Parameter(ValueFromPipeline)][PSTypeName('ManagedDevice')]$target = (Get-PAManagedDevice -Connected)) { | |
foreach ($targetItem in $target) { | |
$result = Invoke-PACommand -target $targetItem -Command 'show vpn flow' | |
$result.IPSec.entry | |
| Select-Object @{N = 'device'; E = { $targetItem } }, id, gwid, name, inner-if, outer-if, localip, peerip, state, ipsec-mode, mon, owner | |
| ForEach-Object { | |
$PSItem.pstypenames.Insert(0, 'VpnSession') | |
$PSItem | |
} | |
} | |
} | |
filter Remove-XmlProperties { | |
Select-Object $PSItem -ExcludeProperty Item, BaseUri, PreviousText, OuterXml, IsReadOnly, HasChildNodes, LastChild, FirstChild, ChildNodes, Value, NextSibling, PreviousSibling, InnerText, InnerXml, SchemaInfo, HasAttributes, Attributes, IsEmpty, OwnerDocument, ParentNode, LocalName, NameSpaceUri, Prefix, NodeType | |
} | |
function Export-PANetworkReport ($Path = './PaloAltoNetworkReport.xlsx', [Parameter(ValueFromPipeline)][PSTypeName('ManagedDevice')]$target = (Get-PAManagedDevice -Connected)) { | |
if (Test-Path $Path) { Write-Warning "Existing Report Found at $Path, removing..."; Remove-Item $Path } | |
$reportPages = @{ | |
'Arp' = { Get-PAArpTable -target $target } | |
'Route' = { Get-PARouteTable -target $target } | |
'Interface' = { Get-PaInterface -target $target } | |
'Vpn' = { Get-PAVpnSession -target $target } | |
} | |
foreach ($page in $reportPages.Keys) { | |
Export-Excel -InputObject (. $reportPages[$page]) -Path $Path -WorksheetName $page -TableName $page -AutoSize | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment