Skip to content

Instantly share code, notes, and snippets.

@JustinGrote
Last active February 26, 2024 17:16
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JustinGrote/7bca185f79d92922531ca06fcaaa5fbb to your computer and use it in GitHub Desktop.
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
#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