Dieser Beitrag ist auch verfügbar auf: English (Englisch)
Mit Hilfe dieses Scriptes zum “Sophos Firewall Host Objekt Import Generator” lassen sich schnell beliebig viele Host Objekte in eine XML zufügen, welche sich im Nachgang schnell in die Firewall importieren lässt – ohne API. Langwieriges Zufügen viele Host Objekte gehört der Vergangenheit an.
Einleitung
Viele werden es kennen: Je nachdem wie viele Hosts man auf der Sophos Firewall anlegen muss, kann der Aufwand sehr hoch werden. Sophos hat für diesen Zweck zwei Möglichkeiten parat: Die API und einen XML Konfigurationsimport.
Die API nutze ich in einigen Fällen auch. Hier ist allerdings der Nachteil, dass viele sich mit API nicht auskennen, sich nicht dran trauen, oder die API für den Zugriff nicht konfiguriert ist. Persönlich bin ich aber ein Freund von Teilimports – die gehen immer und benötigen keine Vorbereitung.
Aus diesem Grund habe ich das unten stehenden PowerShell Script erstellt. Es liest die zu erstellenden Host Objekte aus eine CSV aus und bereitet die Daten für den Import über die GUI vor. Evtl werde ich hier in Zukunft auch eine online Version anbieten – mal sehen 😊
Solltet Ihr Fragen oder Probleme mit dem Script haben, schreibt gerne in die Kommentare.
Beschränkungen
Bei den Eingabedaten gelten die Beschränkungen der Firewall.
- Namen können maximal 60 Zeichen haben
- Beschreibungen können maximal 255 Zeichen haben
Zusätzlich werden von Script nur die folgenden Zeichen für den Namen akzeptiert:
- a-zA-Z0-9_+-#!=*äüöÄÜÖß und das Leerzeichen
Die Beschränkung der erlaubten Zeichen kann beim Scriptaufruf mit dem Parameter -skipCharCheck
aufgehoben werden. Ob die zusätzlichen Zeichen dann zu Fehlern führen, müsst ihr selber testen.
Dieses Script erstellt nur Host Objekte und Gruppen! Für die Erstellung von FQDN Imports haben wir einen anderen Artikel mit einem anderen Tool: Sophos Firewall FQDN Objekt Import Generator
Objekt Beschreibung
Die Beschreibung von Objekten hat erst mit SFOS 20.0.1 Einzug erhalten. Egal welche Version Ihr nutzt, müssen die Felder in der CSV vorkommen. Versionen < SFOS 20.0.1 ignorieren die Daten beim Import. Sind die Felder in der CSV nicht berücksicht, wird der XMl Import nicht erstellt. Leere Beschreibung können gesetzt werden indem das Feld mit einem doppelten Semikolon ;;
übersprungen und leer gelassen wird. Beispiele findet Ihr in den Demo Daten.
Werden Objekte mehrfach einer Gruppe zugewiesen, muss die erste Gruppe eine Beschreibung tragen. Folgende Beschreibungen zur gleichen Gruppe werden ignoriert.
CSV Demo Daten
Host Name;Host Description;Group Name;Group Description;Host Type;IP Address;IP Subnet;IP From;IP To;IP List
Host1;Description for Host1;Group_IP;Description for Group_IP;IP;192.168.1.1;;;;
Host5;;Group_IP;;IP;192.168.5.1;;;;
Host9;Description for Host9;Group_IP;;IP;192.168.9.1;;;;
Host2;Description for Host2;Group_Network;Description for Group_Network;Network;192.168.2.0;24;;;
Host6;;Group_Network;;Network;192.168.6.0;255.255.255.0;;;
Host10;Description for Host10;Group_Network;;Network;192.168.10.0;24;;;
Host3;Description for Host3;Group_Range;Description for Group_Range;Range;;;192.168.3.1;192.168.3.100;
Host7;Description for Host7;Group_Range;;Range;;;192.168.7.1;192.168.7.50;a
Host4;Description for Host4;Group_List;Description for Group_List;List;;;;;192.168.4.1,192.168.4.2,192.168.4.3
Host8;Description for Host8;Group_List;;List;;;;;192.168.8.1,192.168.8.2
Aufbau der CSV
Der Header der CSV kann in der CSV stehen, oder auch weggelassen werden. Soll der Header aber drin stehen, muss dieser immer diese Namensgebung und enthalten und muss auch in englisch bleiben:
Host Name;Host Description;Group Name;Group Description;Host Type;IP Address;IP Subnet;IP From;IP To;IP List
Die CSV muss immer aus 10 Spalten bestehen und auch in der angegebenen Reihenfolge angeordnert sein.
- Host Name
Anzeigename des Hostobjektes (Pflichtfeld) - Host Beschreibung
Die Beschreibung des Hostobjekts (Optional, wird bei SFOS < 20.0.1 ignoriert beim Import) - Gruppen Name
Der Anzeigename der Hostgruppe der der Host zugefügt werden soll (Optional) - Gruppenbeschreibung
Die Beschreibung der Hostgruppe. Kommt die Gruppe mehrfach vor, wird immer nur die Beschreibung des ersten Vorkommens verwendet! (Optional, wird nur einmal pro Hostgruppe benötigt) - Host Typ
Hier wird der Typ des Host Objektes gewählt. Groß- und Kleinschreibung ist egal. Die Namen sind selbstsprechend. (Pflicht)- IP
Hier wird Spalte 6 (IP Adresse) verwendet, 7-10 werden ignoriert - Network
Hier muss in Spalte 6 (IP Adresse) die Netzadresse stehen und in Spalte 7 (Subnetzmaske) die entsprechende Subnetzmaske in voller Schreibweise (z.B.: 255.255.255.0) oder in CIDR Notation (z.B.: 24). Die Spalten 8-10 werden ignoriert - Range
Die Start IP muss in Spalte 8 (IP von) und die End IP muss in Spalte 9 (IP bis) stehen. Die Spalten 6-7, 10 werden ignoriert - List
Die Liste muss mit Komma getrennt in Spalte 10 stehen. Die Spalten 6-9 werden ignoriert
- IP
- IP Adresse
Die IP Adresse (Typ IP) oder Netzmaske (Typ Network) – (Pflicht beim Typ IP oder Network) - Subnetzmaske
Die Netzmaske – (Pflicht beim Typ Network) - IP von
Die Start IP bei einem IP Bereich (Typ Range) – (Pflicht beim Typ Range) - IP bis
Die End IP bei einem IP Bereich (Typ Range) – (Pflicht beim Typ Range) - IP LIste
Eine mit Komma getrennte Liste von IP Adressen – (Pflicht beim Typ List)
Leere Spalten werden einfach mit einem weiteren Semikolon dargestellt. Bitte schaut euch dazu die Demo CSV an, da diese den Aufbau und die Möglichkeiten gut verdeutlicht.
Powershell Script
Wie immer gilt: Die Verwendung des Scripts und der generierten Daten erfolgt komplett auf eigene Gefahr. Wir übernehmen keine Garantie und Haftung für Fehler die hier entstehen könnten. Ein Test in einer Testumgebung wird in allen Fällen empfohlen.
Quellcode und Download
<#
.DESCRIPTION
Creates Sophos XML Import files for hosts objekts from a given CSV file
.PARAMETER -skipCharCheck
Allow all characters in name and description
.PARAMETER -debug
Print created hosts to console
.EXAMPLE
Just start the script. Input and output file are prompted
.NOTES
Demo CSV Data for input can be downloaded at: https://it-tech.wiki/shog
.LINK
More information at https://it-tech.wiki/shog
.AUTHOR
Sebastian Mies
[email protected]
.LICENSE
MIT License
Copyright (c) 2024 Sebastian Mies
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
#>
param (
[switch]$skipCharCheck,
[switch]$debug
)
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
# Function to validate IPv4 Address
function Validate-IPv4Address {
param (
[string]$IPAddress
)
return [System.Net.IPAddress]::TryParse($IPAddress, [ref]$null)
}
# Function to validate Subnet Mask
function Validate-SubnetMask {
param (
[string]$subnetMask
)
$validMasks = @(
"128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0", "248.0.0.0",
"252.0.0.0", "254.0.0.0", "255.0.0.0", "255.128.0.0", "255.192.0.0",
"255.224.0.0", "255.240.0.0", "255.248.0.0", "255.252.0.0", "255.254.0.0",
"255.255.0.0", "255.255.128.0", "255.255.192.0", "255.255.224.0", "255.255.240.0",
"255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0", "255.255.255.128",
"255.255.255.192", "255.255.255.224", "255.255.255.240", "255.255.255.248", "255.255.255.252",
"255.255.255.254", "255.255.255.255"
)
return $validMasks -contains $subnetMask
}
# Function to convert CIDR to full subnet mask
function Convert-CIDRToSubnetMask {
param (
[int]$CIDR
)
if ($CIDR -lt 0 -or $CIDR -gt 32) {
throw "CIDR value must be between 0 and 32."
}
$binaryString = "1" * $CIDR + "0" * (32 - $CIDR)
return ([convert]::ToInt32($binaryString.Substring(0, 8), 2)).ToString() + "." +
([convert]::ToInt32($binaryString.Substring(8, 8), 2)).ToString() + "." +
([convert]::ToInt32($binaryString.Substring(16, 8), 2)).ToString() + "." +
([convert]::ToInt32($binaryString.Substring(24, 8), 2)).ToString()
}
# Function to show open file dialog
function Show-OpenFileDialog {
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.Filter = "CSV files (*.csv)|*.csv"
$OpenFileDialog.ShowDialog() | Out-Null
return $OpenFileDialog.FileName
}
# Function to show save file dialog
function Show-SaveFileDialog {
$SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
$SaveFileDialog.Filter = "XML files (*.xml)|*.xml"
$SaveFileDialog.ShowDialog() | Out-Null
return $SaveFileDialog.FileName
}
# Function to validate allowed characters in strings
function Validate-AllowedCharacters {
param (
[string]$inputString
)
return $inputString -match '^[a-zA-Z0-9_+\-#!=* äüöÄÜÖß]+$'
}
# Ensure console output encoding is correct
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# Detect system language
$language = [System.Globalization.CultureInfo]::CurrentCulture.TwoLetterISOLanguageName
$isGerman = $language -eq 'de'
# Load messages
if ($isGerman) {
$msgMissingHostName = "Zeile {0}: Host Name fehlt. Überspringen."
$msgInvalidColumns = "Zeile {0}: Ungültige Spaltenanzahl. Erwartet: {1}, Tatsächlich: {2}. Überspringen."
$msgInvalidHostName = "Zeile {0}, Host '{1}': Ungültige Zeichen im Host Name. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
$msgInvalidHostDescription = "Zeile {0}, Host '{1}': Ungültige Zeichen in der Host-Beschreibung. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
$msgInvalidGroupName = "Zeile {0}, Host '{1}': Ungültige Zeichen im Gruppenname. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
$msgInvalidGroupDescription = "Zeile {0}, Host '{1}': Ungültige Zeichen in der Gruppenbeschreibung. Überspringen. Verwenden Sie -skipCharCheck, um diese Überprüfung zu umgehen."
$msgTruncateHostName = "Zeile {0}, Host '{1}': Host Name überschreitet 60 Zeichen. Er wird gekürzt."
$msgTruncateHostDescription = "Zeile {0}, Host '{1}': Host-Beschreibung überschreitet 255 Zeichen. Sie wird gekürzt."
$msgMissingIpAddress = "Zeile {0}, Host '{1}': IP-Adresse fehlt für den Host-Typ 'IP'. Überspringen."
$msgInvalidIpAddress = "Zeile {0}, Host '{1}': Ungültige IP-Adresse '{2}'. Überspringen."
$msgMissingNetworkInfo = "Zeile {0}, Host '{1}': IP-Adresse oder Subnetz fehlt für den Host-Typ 'Network'. Überspringen."
$msgInvalidNetworkAddress = "Zeile {0}, Host '{1}': Ungültige Netzwerkadresse '{2}'. Überspringen."
$msgInvalidSubnetMask = "Zeile {0}, Host '{1}': Ungültige Subnetzmaske '{2}'. Überspringen."
$msgInvalidCidr = "Zeile {0}, Host '{1}': Ungültiger CIDR-Wert '{2}'. Überspringen."
$msgMissingRangeInfo = "Zeile {0}, Host '{1}': IP-From oder IP-To fehlt für den Host-Typ 'Range'. Überspringen."
$msgInvalidIpRange = "Zeile {0}, Host '{1}': Ungültiger IP-Bereich '{2} - {3}'. Überspringen."
$msgInvalidRangeOrder = "Zeile {0}, Host '{1}': Ungültiger IP-Bereich '{2} - {3}'. 'IP To' sollte größer als 'IP From' sein. Überspringen."
$msgMissingIpList = "Zeile {0}, Host '{1}': IP-Liste fehlt für den Host-Typ 'List'. Überspringen."
$msgIpListTooLong = "Zeile {0}, Host '{1}': IP-Liste überschreitet 1000 Einträge. Überspringen."
$msgInvalidIpInList = "Zeile {0}, Host '{1}': Ungültige IP-Adresse '{2}' in der IP-Liste. Überspringen."
$msgUnsupportedHostType = "Zeile {0}, Host '{1}': Nicht unterstützter Host-Typ '{2}'. Überspringen."
$msgFileNotSelected = "XML-Dateipfad nicht ausgewählt."
$msgXmlSaved = "XML-Datei wurde in {0} gespeichert"
$msgTarSaved = "XML-Datei wurde in {0} gespeichert"
$msgErrorTar = "Fehler beim Erstellen der TAR-Datei: {0}"
$msgNoArchivingTool = "Kein geeignetes Archivierungstool gefunden. Bitte die Datei manuell in ein TAR-Archiv ohne Komprimierung mit dem Namen Entities.xml verschieben, bevor sie in die Firewall importiert wird."
$msgCreatedXml = "XML-Datei erstellt: {0} - {1}"
} else {
$msgMissingHostName = "Row {0}: Missing Host Name. Skipping."
$msgInvalidColumns = "Row {0}: Invalid number of columns. Expected: {1}, Actual: {2}. Skipping."
$msgInvalidHostName = "Row {0}, Host '{1}': Invalid characters in Host Name. Skipping. Use -skipCharCheck to bypass this check."
$msgInvalidHostDescription = "Row {0}, Host '{1}': Invalid characters in Host Description. Skipping. Use -skipCharCheck to bypass this check."
$msgInvalidGroupName = "Row {0}, Host '{1}': Invalid characters in Group Name. Skipping. Use -skipCharCheck to bypass this check."
$msgInvalidGroupDescription = "Row {0}, Host '{1}': Invalid characters in Group Description. Skipping. Use -skipCharCheck to bypass this check."
$msgTruncateHostName = "Row {0}, Host '{1}': Host Name exceeds 60 characters. It will be truncated."
$msgTruncateHostDescription = "Row {0}, Host '{1}': Host Description exceeds 255 characters. It will be truncated."
$msgMissingIpAddress = "Row {0}, Host '{1}': Missing IP Address for Host Type 'IP'. Skipping."
$msgInvalidIpAddress = "Row {0}, Host '{1}': Invalid IP Address '{2}'. Skipping."
$msgMissingNetworkInfo = "Row {0}, Host '{1}': Missing IP Address or Subnet for Host Type 'Network'. Skipping."
$msgInvalidNetworkAddress = "Row {0}, Host '{1}': Invalid Network Address '{2}'. Skipping."
$msgInvalidSubnetMask = "Row {0}, Host '{1}': Invalid Subnet Mask '{2}'. Skipping."
$msgInvalidCidr = "Row {0}, Host '{1}': Invalid CIDR value '{2}'. Skipping."
$msgMissingRangeInfo = "Row {0}, Host '{1}': Missing IP From or IP To for Host Type 'Range'. Skipping."
$msgInvalidIpRange = "Row {0}, Host '{1}': Invalid IP Range '{2} - {3}'. Skipping."
$msgInvalidRangeOrder = "Row {0}, Host '{1}': Invalid IP Range '{2} - {3}'. 'IP To' should be greater than 'IP From'. Skipping."
$msgMissingIpList = "Row {0}, Host '{1}': Missing IP List for Host Type 'List'. Skipping."
$msgIpListTooLong = "Row {0}, Host '{1}': IP List exceeds 1000 entries. Skipping."
$msgInvalidIpInList = "Row {0}, Host '{1}': Invalid IP Address '{2}' in IP List. Skipping."
$msgUnsupportedHostType = "Row {0}, Host '{1}': Unsupported Host Type '{2}'. Skipping."
$msgFileNotSelected = "XML file path not selected."
$msgXmlSaved = "XML file saved to {0}"
$msgTarSaved = "XML file saved to {0}"
$msgErrorTar = "Error creating TAR file: {0}"
$msgNoArchivingTool = "No suitable archiving tool found. Please manually move the file into a TAR archive named Entities.xml without compression before importing it into the firewall."
$msgCreatedXml = "Created XML: {0} - {1}"
}
# Load CSV file
$csvPath = Show-OpenFileDialog
if (-not $csvPath) {
Write-Error $msgFileNotSelected
exit
}
# Import CSV with semicolon delimiter
$csvData = Import-Csv -Path $csvPath -Delimiter ";"
# Check for correct number of columns
$requiredColumns = 10
$validData = @()
$firstLine = $true
foreach ($line in Get-Content -Path $csvPath) {
# Ignore the first line if it contains the column headers
if ($firstLine) {
$firstLine = $false
if ($line -match '^Host Name;Host Description;Group Name;Group Description;Host Type;IP Address;IP Subnet;IP From;IP To;IP List$') {
continue
}
}
$columns = $line -split ";"
if ($columns.Length -ne $requiredColumns) {
Write-Warning ($msgInvalidColumns -f $rowIndex, $requiredColumns, $columns.Length)
} else {
$validData += ,($line -split ";")
}
}
# Initialize XML Document
[xml]$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDeclaration = $xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", $null)
$null = $xmlDoc.AppendChild($xmlDeclaration)
$configurationElement = $xmlDoc.CreateElement("Configuration")
$configurationElement.SetAttribute("APIVersion", "2000.2")
$configurationElement.SetAttribute("IS_WIFI6", "0")
$configurationElement.SetAttribute("IPS_CAT_VER", "1")
$null = $xmlDoc.AppendChild($configurationElement)
$hostGroups = @{}
# Process each row in CSV
$rowIndex = 0
foreach ($row in $validData) {
$rowIndex++
$hostName = $row[0].Trim()
if (-not $hostName) {
Write-Warning ($msgMissingHostName -f $rowIndex)
continue
}
$hostDescription = $row[1].Trim()
$groupName = $row[2].Trim()
$groupDescription = $row[3].Trim()
$hostType = $row[4].Trim()
$ipAddress = $row[5].Trim()
$ipSubnet = $row[6].Trim()
$ipFrom = $row[7].Trim()
$ipTo = $row[8].Trim()
$ipList = $row[9].Trim()
# Validate allowed characters in Host Name and Description
if (-not $skipCharCheck) {
if (-not (Validate-AllowedCharacters -inputString $hostName)) {
Write-Warning ($msgInvalidHostName -f $rowIndex, $hostName)
continue
}
if ($hostDescription -and -not (Validate-AllowedCharacters -inputString $hostDescription)) {
Write-Warning ($msgInvalidHostDescription -f $rowIndex, $hostName)
continue
}
if ($groupName -and -not (Validate-AllowedCharacters -inputString $groupName)) {
Write-Warning ($msgInvalidGroupName -f $rowIndex, $hostName)
continue
}
if ($groupDescription -and -not (Validate-AllowedCharacters -inputString $groupDescription)) {
Write-Warning ($msgInvalidGroupDescription -f $rowIndex, $hostName)
continue
}
}
# Validate and truncate Host Name
if ($hostName.Length -gt 60) {
Write-Warning ($msgTruncateHostName -f $rowIndex, $hostName)
$hostName = $hostName.Substring($hostName.Length - 60)
}
# Validate and truncate Host Description
if ($hostDescription.Length -gt 255) {
Write-Warning ($msgTruncateHostDescription -f $rowIndex, $hostName)
$hostDescription = $hostDescription.Substring($hostDescription.Length - 255)
}
# Validate Host Type and required fields
$isValid = $true
switch -Regex ($hostType.ToLower()) {
'^ip$' {
if (-not $ipAddress) {
Write-Warning ($msgMissingIpAddress -f $rowIndex, $hostName)
$isValid = $false
}
elseif (-not (Validate-IPv4Address -IPAddress $ipAddress)) {
Write-Warning ($msgInvalidIpAddress -f $rowIndex, $hostName, $ipAddress)
$isValid = $false
}
else {
$hostType = "IP"
}
}
'^network$' {
if (-not $ipAddress -or -not $ipSubnet) {
Write-Warning ($msgMissingNetworkInfo -f $rowIndex, $hostName)
$isValid = $false
}
elseif (-not (Validate-IPv4Address -IPAddress $ipAddress)) {
Write-Warning ($msgInvalidNetworkAddress -f $rowIndex, $hostName, $ipAddress)
$isValid = $false
}
else {
try {
if ($ipSubnet -match '^\d+$') {
$ipSubnetInt = [int]$ipSubnet
$ipSubnet = Convert-CIDRToSubnetMask -CIDR $ipSubnetInt
}
elseif (-not (Validate-SubnetMask -subnetMask $ipSubnet)) {
Write-Warning ($msgInvalidSubnetMask -f $rowIndex, $hostName, $ipSubnet)
$isValid = $false
}
} catch {
Write-Warning ($msgInvalidCidr -f $rowIndex, $hostName, $ipSubnet)
$isValid = $false
}
$hostType = "Network"
}
}
'^range$' {
if (-not $ipFrom -or -not $ipTo) {
Write-Warning ($msgMissingRangeInfo -f $rowIndex, $hostName)
$isValid = $false
}
elseif (-not (Validate-IPv4Address -IPAddress $ipFrom) -or -not (Validate-IPv4Address -IPAddress $ipTo)) {
Write-Warning ($msgInvalidIpRange -f $rowIndex, $hostName, $ipFrom, $ipTo)
$isValid = $false
}
else {
$startIP = [System.Net.IPAddress]::Parse($ipFrom).GetAddressBytes()
$endIP = [System.Net.IPAddress]::Parse($ipTo).GetAddressBytes()
if ([BitConverter]::ToUInt32($startIP, 0) -ge [BitConverter]::ToUInt32($endIP, 0)) {
Write-Warning ($msgInvalidRangeOrder -f $rowIndex, $hostName, $ipFrom, $ipTo)
$isValid = $false
}
$hostType = "IPRange"
}
}
'^list$' {
if (-not $ipList) {
Write-Warning ($msgMissingIpList -f $rowIndex, $hostName)
$isValid = $false
}
else {
$ipListArray = $ipList -split ',\s*'
if ($ipListArray.Length -gt 1000) {
Write-Warning ($msgIpListTooLong -f $rowIndex, $hostName)
$isValid = $false
}
else {
$validIPs = $ipListArray | ForEach-Object {
if (-not (Validate-IPv4Address -IPAddress $_)) {
Write-Warning ($msgInvalidIpInList -f $rowIndex, $hostName, $_)
$false
} else {
$true
}
}
if ($validIPs -contains $false) {
$isValid = $false
}
}
$hostType = "IPList"
}
}
default {
Write-Warning ($msgUnsupportedHostType -f $rowIndex, $hostName, $hostType)
$isValid = $false
}
}
# If valid, create IPHost element
if ($isValid) {
$ipHostElement = $xmlDoc.CreateElement("IPHost")
$ipHostElement.SetAttribute("transactionid", "")
$nameElement = $xmlDoc.CreateElement("Name")
$nameElement.InnerText = $hostName
$null = $ipHostElement.AppendChild($nameElement)
$descriptionElement = $xmlDoc.CreateElement("Description")
if ($hostDescription) {
$descriptionElement.InnerText = $hostDescription
} else {
$descriptionElement.IsEmpty = $true
}
$null = $ipHostElement.AppendChild($descriptionElement)
$ipFamilyElement = $xmlDoc.CreateElement("IPFamily")
$ipFamilyElement.InnerText = "IPv4"
$null = $ipHostElement.AppendChild($ipFamilyElement)
$hostTypeElement = $xmlDoc.CreateElement("HostType")
$hostTypeElement.InnerText = $hostType
$null = $ipHostElement.AppendChild($hostTypeElement)
switch ($hostType) {
"IP" {
$ipAddressElement = $xmlDoc.CreateElement("IPAddress")
$ipAddressElement.InnerText = $ipAddress
$null = $ipHostElement.AppendChild($ipAddressElement)
}
"Network" {
$ipAddressElement = $xmlDoc.CreateElement("IPAddress")
$ipAddressElement.InnerText = $ipAddress
$null = $ipHostElement.AppendChild($ipAddressElement)
$subnetElement = $xmlDoc.CreateElement("Subnet")
$subnetElement.InnerText = $ipSubnet
$null = $ipHostElement.AppendChild($subnetElement)
}
"IPRange" {
$startIPAddressElement = $xmlDoc.CreateElement("StartIPAddress")
$startIPAddressElement.InnerText = $ipFrom
$null = $ipHostElement.AppendChild($startIPAddressElement)
$endIPAddressElement = $xmlDoc.CreateElement("EndIPAddress")
$endIPAddressElement.InnerText = $ipTo
$null = $ipHostElement.AppendChild($endIPAddressElement)
}
"IPList" {
$listOfIPAddressesElement = $xmlDoc.CreateElement("ListOfIPAddresses")
$listOfIPAddressesElement.InnerText = ($ipListArray -join ",")
$null = $ipHostElement.AppendChild($listOfIPAddressesElement)
}
}
$null = $configurationElement.AppendChild($ipHostElement)
if ($debug) {
Write-Host ($msgCreatedXml -f $hostName, $hostType) -ForegroundColor Blue
}
}
# Process Group Name and Description
if ($groupName) {
if ($groupName.Length -gt 60) {
Write-Warning ($msgTruncateHostName -f $rowIndex, $groupName)
$groupName = $groupName.Substring($groupName.Length - 60)
}
if ($groupDescription.Length -gt 255) {
Write-Warning ($msgTruncateHostDescription -f $rowIndex, $groupName)
$groupDescription = $groupDescription.Substring($groupDescription.Length - 255)
}
if (-not $hostGroups.ContainsKey($groupName)) {
$hostGroups[$groupName] = @{
Description = $groupDescription
Hosts = @()
}
}
$hostGroups[$groupName].Hosts += $hostName
}
}
# Create IPHostGroup elements
foreach ($groupName in $hostGroups.Keys) {
$group = $hostGroups[$groupName]
$ipHostGroupElement = $xmlDoc.CreateElement("IPHostGroup")
$ipHostGroupElement.SetAttribute("transactionid", "")
$groupNameElement = $xmlDoc.CreateElement("Name")
$groupNameElement.InnerText = $groupName
$null = $ipHostGroupElement.AppendChild($groupNameElement)
$groupDescriptionElement = $xmlDoc.CreateElement("Description")
if ($group.Description) {
$groupDescriptionElement.InnerText = $group.Description
} else {
$groupDescriptionElement.IsEmpty = $true
}
$null = $ipHostGroupElement.AppendChild($groupDescriptionElement)
$hostListElement = $xmlDoc.CreateElement("HostList")
foreach ($hostName in $group.Hosts) {
$hostElement = $xmlDoc.CreateElement("Host")
$hostElement.InnerText = $hostName
$null = $hostListElement.AppendChild($hostElement)
}
$null = $ipHostGroupElement.AppendChild($hostListElement)
$ipFamilyElement = $xmlDoc.CreateElement("IPFamily")
$ipFamilyElement.InnerText = "IPv4"
$null = $ipHostGroupElement.AppendChild($ipFamilyElement)
$null = $configurationElement.AppendChild($ipHostGroupElement)
if ($debug) {
Write-Host ($msgCreatedXml -f $groupName, "Group") -ForegroundColor Blue
}
}
# Save XML to file
$xmlPath = Show-SaveFileDialog
if (-not $xmlPath) {
Write-Error $msgFileNotSelected
exit
}
$entitiesXmlPath = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($xmlPath), "Entities.xml")
$xmlDoc.Save($entitiesXmlPath)
Write-Host ($msgXmlSaved -f $entitiesXmlPath) -ForegroundColor Green
# Create TAR archive
$tarPath = [System.IO.Path]::ChangeExtension($xmlPath, ".tar")
# Check for tar.exe
$tarExePath = "$env:SystemRoot\System32\tar.exe"
$tarCreated = $false
if (Test-Path $tarExePath) {
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $tarExePath
$startInfo.Arguments = "-cf `"$tarPath`" -C `"$([System.IO.Path]::GetDirectoryName($entitiesXmlPath))`" Entities.xml"
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $true
$process = [System.Diagnostics.Process]::Start($startInfo)
$stdout = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
$process.WaitForExit()
if ($process.ExitCode -eq 0 -and (Test-Path $tarPath)) {
$tarCreated = $true
Write-Host ($msgTarSaved -f $tarPath) -ForegroundColor Green
} else {
Write-Error ($msgErrorTar -f $stderr)
}
}
# Check for 7-Zip in standard and other paths
if (-not $tarCreated) {
$sevenZipPaths = @(
"$env:ProgramFiles\7-Zip\7z.exe",
"$env:ProgramFiles(x86)\7-Zip\7z.exe"
)
$sevenZipPath = $sevenZipPaths | Where-Object { Test-Path $_ }
if (-not $sevenZipPath) {
$sevenZipPath = Get-Command 7z -ErrorAction SilentlyContinue
}
if ($sevenZipPath) {
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = $sevenZipPath
$startInfo.Arguments = "a -ttar `"$tarPath`" `"$entitiesXmlPath`""
$startInfo.RedirectStandardOutput = $true
$startInfo.RedirectStandardError = $true
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $true
$process = [System.Diagnostics.Process]::Start($startInfo)
$stdout = $process.StandardOutput.ReadToEnd()
$stderr = $process.StandardError.ReadToEnd()
$process.WaitForExit()
if ($process.ExitCode -eq 0 -and (Test-Path $tarPath)) {
$tarCreated = $true
Write-Host ($msgTarSaved -f $tarPath) -ForegroundColor Green
} else {
Write-Error ($msgErrorTar -f $stderr)
}
}
}
# If no suitable archive tool is found
if (-not $tarCreated) {
Write-Warning $msgNoArchivingTool
}
# Delete the additional Entities.xml file if it is not the original XML
if ($entitiesXmlPath -ne $xmlPath) {
Remove-Item -Path $entitiesXmlPath -Force
}
Nutzung des Script
Das Script wird einfach in der Powershell aufgerufen. Die Eingabe Datei (CSV) und die Ausgabedatei (XML) wird interaktiv abgefragt über einen Dialog.
Sofern auf dem System die tar.exe im System32 Verzeichnis existiert, oder 7zip installiert ist, wird auch die nötige TAR Datei für den Import in die Firewall erstellt.
Keine TAR Datei erhalten?
Ist weder tar.exe noch 7zip vorhanden auf dem System, müsst Ihr die ausgegebene Datei selber als Entities.xml
in ein unkomprimiertes TAR Archiv packen. Heißt die Datei im Archiv anders, wird der Import ignoriert!
Fehler: ExecutionPolicy / nicht digital signiert
Erhaltet Ihr einen Fehler der auf eine Reglementierung durch die ExecutionPolicy hinweist, müsst Ihr die ExecutionPolicy mit Set-ExecutionPolicy
anpassen. Das Script von uns ist nicht signiert. Daher ist der Fehler auf einem Standard System zu erwarten. Die Anpassung sollte immer durch erfahrenes Personal erfolgen und nach Möglichkeit auch im Nachgang wieder Rückgängig gemacht werden. Näher Informationen zur Anpassung und Empfehlungen werden wir an dieser Stelle aber bewusst nicht ausgeben. Also entweder Google nutzen, oder erfahrenes Personal zu dem Thema ansprechen. Wir bitten um Verstandnis!
Parameter zum Aufruf
- -skipCharCheck
Führt keine Prüfung der erlaubten Zeichen durch. - -debug
Gibt in der Kommandozeile aus welche Hosts oder Gruppen eingelesen wurden
Import der XML in die Firewall
Über das Webinterface lässt sich die Datei in die Firewall importieren. Anschließend sind die Hosts angelegt.
- Es muss immer die TAR Datei hochgeladen werden. Andere Formate werden ignoriert. Kein TAR Archiv erhalten, dann schaut unter “Keine TAR Datei erhalten“
- Sind in der importierten Datei seht viele Hosts, kann der Import auch mal etwas länger dauern. Generell ist die Geschwindigkeit aber auch von der vrwendeten Hardware abhängig! Bringt also ggf. etwas Geduld mit.
Danke für das Script!!!
Ich habe schon den FQDN Generator mit Freude benutzt. Jetzt eine Möglichkeit schnell die Hosts anzulegen. Einfach Top! Bin schon gespannt was hier noch so kommt. 😀
Danke für Dein Feedback, und prima, dass es auch außerhalb meiner eigenen Tests funtioniert! 😊
Ein paar Ideen habe ich noch. Mal sehen wann ich weiter Zeit finde…