With the help of this script for the "Sophos Firewall Host Object Import Generator", any number of host objects can be quickly added to an XML, which can then be quickly imported into the firewall - without an API. Tedious adding of many host objects is a thing of the past.
Introduction
Many people will be familiar with this: Depending on how many hosts you have to create on the Sophos firewall, the effort can be very high. Sophos has two options for this purpose: the API and an XML configuration import.
I also use the API in some cases. However, the disadvantage here is that many people are not familiar with APIs, don't dare to use them, or the API is not configured for access. Personally, however, I am a friend of partial imports - they always work and require no preparation.
For this reason, I have created the PowerShell script below. It reads the host objects to be created from a CSV and prepares the data for import via the GUI. Maybe I will also offer an online version here in the future - let's see 😊
If you have any questions or problems with the script, please write in the comments.
Restrictions
The restrictions of the firewall apply to the input data.
- Names can have a maximum of 60 characters
- Descriptions can have a maximum of 255 characters
In addition, Script only accepts the following characters for the name:
- a-zA-Z0-9_+-#!=*äüöÄÜÖß and the space character
Object Description
The description of objects has only been introduced with SFOS 20.0.1.
Regardless of which version you use, the fields must appear in the CSV.
Versions < SFOS 20.0.1 ignore the data during import.
If the fields are not included in the CSV, the XMl import will not be created.
Empty descriptions can be set by skipping the field with a double semicolon ;;
and leaving it empty.
Examples can be found in the demo data.
If objects are assigned to a group more than once, the first group must have a description.
Subsequent descriptions for the same group are ignored.
CSV demo data
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
Structure of the CSV
The header of the CSV can be included in the CSV or omitted. However, if the header is to be included, it must always contain this naming and must also remain in English:
Host Name;Host Description;Group Name;Group Description;Host Type;IP Address;IP Subnet;IP From;IP To;IP List
The CSV must always consist of 10 columns and must also be arranged in the specified order.
- Host name
Display name of the host object (mandatory field) - Host description
The description of the host object (optional, is ignored for SFOS < 20.0.1 during import) - Group name
The display name of the host group to which the host is to be added (optional) - Group description
The description of the host group. If the group occurs more than once, only the description of the first occurrence is used! (Optional, only required once per host group) - Host type
The type of host object is selected here. Upper and lower case is not important. The names are self-explanatory. (compulsory)- IP ADDRESS
Column 6 (IP address) is used here, 7-10 are ignored - Network
Column 6 (IP address) must contain the network address and column 7 (subnet mask) the corresponding subnet mask in full notation (e.g.: 255.255.255.0) or in CIDR notation (e.g.: 24). Columns 8-10 are ignored - Range
The Start IP must be in column 8 (IP from) and the End IP must be in column 9 (IP to). Columns 6-7, 10 are ignored - List
The list must be separated by a comma in column 10. Columns 6-9 are ignored
- IP ADDRESS
- IP address
The IP address (type IP) or netmask (type Network) - (mandatory for type IP or Network) - Subnet mask
The netmask - (mandatory for the Network type) - IP from
The start IP for an IP range (range type) - (mandatory for range type) - IP to
The end IP for an IP range (type Range) - (mandatory for type Range) - IP LIste
A comma-separated list of IP addresses - (mandatory for type List)
Empty columns are simply displayed with another semicolon. Please take a look at the CSV demo, as this illustrates the structure and possibilities well.
Powershell Script
Source code and 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
}
Use of the script
The script is simply called up in Powershell.
The input file (CSV) and the output file (XML) are queried interactively via a dialog.
If the tar.exe exists in the System32 directory on the system, or 7zip is installed, the TAR file required for the import into the firewall is also created.
No TAR file received?
If neither tar.exe nor 7zip is available on the system, you must pack the output file yourself as Entities.xml
in an uncompressed TAR archive.
If the file in the archive has a different name, the import will be ignored!
Error: ExecutionPolicy / not digitally signed
If you receive an error that indicates a regulation by the ExecutionPolicy, you must adjust the ExecutionPolicy with Set-ExecutionPolicy
.
Our script is not signed.
The error is therefore to be expected on a standard system.
The adjustment should always be carried out by experienced personnel and, if possible, reversed afterwards.
However, we will deliberately not provide more detailed information on customization and recommendations at this point.
So either use Google or speak to experienced personnel on the subject.
We ask for your understanding!
Parameters for calling
- -skipCharCheck
Does not check the permitted characters. - -debug
Outputs in the command line which hosts or groups have been read in
Importing the XML into the firewall
The file can be imported into the firewall via the web interface. The hosts are then created.
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…
Danke! Mit dem Script kann ich Listen von Objekten endlich schneller importieren!