Skip to content

Instantly share code, notes, and snippets.

@quonic
Last active May 29, 2021 00:14
Show Gist options
  • Save quonic/8ab8c5454df3491a98666ac0ff84c6cf to your computer and use it in GitHub Desktop.
Save quonic/8ab8c5454df3491a98666ac0ff84c6cf to your computer and use it in GitHub Desktop.
Appache Guacamole user-mappings.xml file generator for current network
. .\NewXMLDocument.ps1
$rdp_domainname = "MicrosoftAccount"
$rdp_username = "fred"
$ssh_username = "fred"
$vnc_username = "fred"
$rdp_password = "changeme!"
$ssh_password = "changeme!"
$vnc_password = "changeme!"
$Network = "192.168.1."
$NetworkAddresses = 1..254 | ForEach-Object {
@{
ComputerName = "$($Network)$($_)"
}
}
$Connections = $NetworkAddresses | ForEach-Object {
if (Test-Connection -ComputerName $_ -Count 1 -TimeToLive 10) {
if ((Test-NetConnection -ComputerName $_ -Port 22).TcpTestSucceeded) {
$ssh = $true
}
if ((Test-NetConnection -ComputerName $_ -Port 5900).TcpTestSucceeded) {
$vnc = $true
}
if ((Test-NetConnection -ComputerName $_ -Port 3389).TcpTestSucceeded) {
$rdp = $true
}
[PSCustomObject]@{
'address' = $_
'ssh' = $ssh
'vnc' = $vnc
'rdp' = $rdp
}
}
}
## used for testing
# $Connections = @(
# [PSCustomObject]@{
# 'address' = "192.168.1.1"
# 'ssh' = $true
# 'vnc' = $false
# 'rdp' = $false
# }
# [PSCustomObject]@{
# 'address' = "192.168.1.2"
# 'ssh' = $false
# 'vnc' = $false
# 'rdp' = $true
# }
# [PSCustomObject]@{
# 'address' = "192.168.1.3"
# 'ssh' = $true
# 'vnc' = $false
# 'rdp' = $true
# }
# )
$xml = New-XmlDocument -ScriptBlock {
user-mapping {
authorize {
username = $ssh_username
password = $ssh_password
$Connections | ForEach-Object {
$Address = $_.address
if ($_.rdp) {
connection {
name = "$([System.Net.Dns]::gethostentry($Address)) Desktop(RDP)"
#name = "$Address Desktop(RDP)" # used for testing
protocol {
'rdp'
}
param {
name = "hostname"
"$Address"
}
param {
name = "security"
'nla'
}
param {
name = "ignore-cert"
'true'
}
param {
name = "domain"
"$rdp_domainname"
}
param {
name = "username"
'${GUAC_USERNAME}'
}
param {
name = "password"
'${GUAC_PASSWORD}'
}
}
}
elseif ($_.vnc) {
connection {
name = "$([System.Net.Dns]::gethostentry($Address)) Desktop(VNC)"
#name = "$Address Desktop(VNC)" # used for testing
protocol {
'vnc'
}
param {
name = "hostname"
"$Address"
}
param {
name = "username"
'${GUAC_USERNAME}'
}
param {
name = "password"
'${GUAC_PASSWORD}'
}
}
}
if ($_.ssh) {
connection {
name = "$([System.Net.Dns]::gethostentry($Address)) Terminal"
#name = "$Address Terminal" # used for testing
protocol {
'ssh'
}
param {
name = "hostname"
"$Address"
}
param {
name = "port"
'22'
}
param {
name = "username"
'${GUAC_USERNAME}'
}
param {
name = "password"
'${GUAC_PASSWORD}'
}
}
}
}
}
}
}
## For testing
#$xml.ToString()
# remove old user mappings
Remove-Item "/etc/guacamole/user-mappings.xml" -Force
# Output $xml to "/etc/guacamole/user-mappings.xml"
$xml.Save("/etc/guacamole/user-mappings.xml")
#requires -Version 5.1
using assembly System.Xml.Linq
<#
.SYNOPSIS
Easy DSL for generating XML documents.
.DESCRIPTION
The NewXmlDocument script creates a new XML document using a dynamic DSL (Domain Specific Language).
.PARAMETER ScriptBlock
Specifies a script block that contains XML elements as command names. Any command in the script block
that does not have a command that is loaded into the current session will be treated as an XML element.
To create an element, use the element name as the command name, and pass a script block as a
argument.
For example, this command would create the XML "<Description>description here</Description>".
Description { 'description here' }
You can use any resolvable command, variable, etc to return the value assigned to the element.
To create an attribute, use the same syntax you would in a hashtable definition.
For example, this command
Author {
Name = "Jim"
}
Would create the XML <Author Name="Jim" />
The syntax for these can be combined and nested to form a full XML document (see examples for more
details)
.PARAMETER Namespace
Specifies the namespace for the XML document. This is the only valid way to define the namespace,
if you try to define it with a "xmlns" attribute an exception will be thrown.
.PARAMETER FilePath
Specifies the file path to save the XML document to.
.PARAMETER PassThru
If specified with the "FilePath" parameter, the XElement will be returned to the pipeline as well
as written to XML. This parameter has no effect if "FilePath" is not present.
.INPUTS
None
This script does not accept input from the pipeline
.OUTPUTS
System.Xml.Linq.XElement
The completed XML element will be returned to the pipeline.
.EXAMPLE
PS C:\> $xml = NewXmlDocument.ps1 -FilePath '.\Authors.xml' {
>> Authors {
>> Author {
>> Name = 'John'
>> Age = 30
>> }
>> Author {
>> Name = 'Tim'
>> Age = 10
>> 'Writes about horror'
>> }
>> }
>>}
Produces an XML file with the following content:
<?xml version="1.0" encoding="utf-8"?>
<Authors>
<Author Name="John" Age="30" />
<Author Name="Tim" Age="10">Writes about horror</Author>
</Authors>
.EXAMPLE
PS C:\> $plaster = NewXmlDocument.ps1 -Namespace 'http://www.microsoft.com/schemas/PowerShell/Plaster/v1' {
>> plasterManifest {
>> schemaVersion = '1.0'
>> metadata {
>> name { 'TestManifest' }
>> id { (New-Guid).Guid }
>> version { '0.1.0' }
>> title { 'My Plaster Manifest' }
>> description { 'A plaster manifest created to test this function.' }
>> }
>> parameters {
>> parameter {
>> name = 'ModuleName'
>> type = 'Text'
>> prompt = 'Enter the name of the module'
>> }
>> }
>> content {
>> file {
>> source = '_module.psm1'
>> destination = '${PLASTER_PARAM_ModuleName}.psd1'
>> }
>> requireModule {
>> name = 'Pester'
>> minimumVersion = '3.4.0'
>> message = 'Without Pester, you will not be able to run tests!'
>> }
>> }
>> }
>>}
PS C:\> $xml.Save('.\plasterManifest.xml')
Produces an working plaster manifest XML document with the following content:
<?xml version="1.0" encoding="utf-8"?>
<plasterManifest schemaVersion="1.0" xmlns="http://www.microsoft.com/schemas/PowerShell/Plaster/v1">
<metadata>
<name>TestManifest</name>
<id>3598072f-578d-42f7-b576-101cadc5efce</id>
<version>0.1.0</version>
<title>My Plaster Manifest</title>
<description>A plaster manifest created to test this function.</description>
</metadata>
<parameters>
<parameter name="ModuleName" type="Text" prompt="Enter the name of the module" />
</parameters>
<content>
<file source="_module.psm1" destination="${PLASTER_PARAM_ModuleName}.psd1" />
<requireModule name="Pester" minimumVersion="3.4.0" message="Without Pester, you will not be able to run tests!" />
</content>
</plasterManifest>
.NOTES
The following changes are made to command lookup while the DSL is running:
- Module Autoloading is set to ModuleQualified
- Aliases are disabled
- The "prompt" function is disabled
- Resolving "Get-*" commands with only the noun is disabled
- All command lookup failures are routed to a function in this script
#>
function New-XmlDocument {
[CmdletBinding(PositionalBinding = $false, SupportsShouldProcess = $false)]
[OutputType('System.Xml.Linq.XElement')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
param(
[Parameter(Mandatory, Position = 0)]
[ValidateNotNullOrEmpty()]
[scriptblock]
$ScriptBlock,
[ValidateNotNullOrEmpty()]
[Alias('Path')]
[string]
$FilePath,
$Namespace
)
begin {
function New-XmlElement {
[CmdletBinding(DefaultParameterSetName = 'Body')]
[OutputType('System.Xml.Linq.XElement', ParameterSetName = 'Element')]
[OutputType('System.Xml.Linq.XAttribute', ParameterSetName = 'Attribute')]
param(
[Parameter(Position = 0, Mandatory, ParameterSetName = 'Element')]
[scriptblock]
$Body,
[Parameter(Position = 0, Mandatory, ParameterSetName = 'Attribute')]
[ValidateSet('=')]
[string]
$Operator,
[Parameter(Position = 1, Mandatory, ParameterSetName = 'Attribute')]
[string]
$Text
)
end {
$elementName = [System.Xml.Linq.XName]($MyInvocation.InvocationName)
if ($Namespace) {
$elementName = [System.Xml.Linq.XNamespace]$Namespace + $elementName
}
switch ($PSCmdlet.ParameterSetName) {
Attribute {
[System.Xml.Linq.XAttribute]::new($MyInvocation.InvocationName, $Text)
}
Element {
if ($Body -and ($output = . $Body)) {
$element = [System.Xml.Linq.XElement]::new($elementName)
foreach ($item in $output) {
$element.Add($item)
}
$element
}
else {
[System.Xml.Linq.XElement]::new($elementName)
}
}
}
}
}
}
end {
try {
# Disable module autoloading so command lookup doesn't take forever to fail.
$originalPreference = $PSModuleAutoLoadingPreference
$PSModuleAutoLoadingPreference = 'ModuleQualified'
# Add post lookup action to override some command lookup results
$originalPCLA = $ExecutionContext.SessionState.InvokeCommand.PostCommandLookupAction
$ExecutionContext.SessionState.InvokeCommand.PostCommandLookupAction = {
param(
[string]
$commandName,
[System.Management.Automation.CommandLookupEventArgs]
$lookupEventArgs
)
# Command lookup will prepend Get when looking up verbless commands.
$isPrependedGet = $lookupEventArgs.Command.Name -match 'Get-(\w+)' -and
$Matches[1] -eq $commandName
# Skip aliases
$isAlias = 'Alias' -eq $lookupEventArgs.Command.CommandType
# Skip the commands that don't fit Noun-Verb format.
$isNonStandardFormat = $lookupEventArgs.Command.Name -notmatch '\w+-\w+'
if ($isPrependedGet -or $isAlias -or $isNonStandardFormat) {
$lookupEventArgs.Command = $ExecutionContext.SessionState.InvokeCommand.GetCommand(
'New-XmlElement',
'Function')
}
}
# Add command not found action that returns New-XmlElement for any command lookup failures
$originalCNFA = $ExecutionContext.SessionState.InvokeCommand.CommandNotFoundAction
$ExecutionContext.SessionState.InvokeCommand.CommandNotFoundAction = {
param(
[string]
$commandName,
[System.Management.Automation.CommandLookupEventArgs]
$lookupEventArgs
)
$lookupEventArgs.Command = $ExecutionContext.SessionState.InvokeCommand.GetCommand(
'New-XmlElement',
'Function')
}
# Any unknown commands in the script block will be treated as XML elements.
$xml = $ScriptBlock.Invoke()
if (-not $xml) { return }
if ($FilePath) {
try {
$resolved = $PSCmdlet.SessionState.Path.
GetUnresolvedProviderPathFromPSPath($FilePath)
$xml.Save($resolved)
}
catch {
$exception = $PSItem -as [Exception]
if (-not $exception) { $exception = $PSItem.Exception }
if (-not $exception) { $exception = $PSItem.InnerException }
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
$exception,
'FailureSavingXml',
[System.Management.Automation.ErrorCategory]::WriteError,
$FilePath))
}
}
if (-not $FilePath -or $PassThru.IsPresent) {
$xml # yield
}
}
finally {
$PSModuleAutoLoadingPreference = $originalPreference
$ExecutionContext.SessionState.InvokeCommand.CommandNotFoundAction = $originalCNFA
$ExecutionContext.SessionState.InvokeCommand.PostCommandLookupAction = $originalPCLA
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment