├── .gitignore ├── LICENSE ├── RDCManager ├── InitializeModule.ps1 ├── RdcManager.psd1 ├── RdcManager.psm1 ├── img │ ├── ZMRivZa5sA.png │ └── xIkQfDVql2.png ├── private │ ├── ADSI │ │ ├── GetAdsiComputer.ps1 │ │ ├── GetAdsiObject.ps1 │ │ ├── GetAdsiOrganizationalUnit.ps1 │ │ ├── GetAdsiRootDSE.ps1 │ │ └── NewDirectoryEntry.ps1 │ └── CommandAdapter │ │ ├── GetADComputer.ps1 │ │ ├── GetADObject.ps1 │ │ └── GetADOrganizationalUnit.ps1 └── public │ ├── Configuration │ ├── Get-RdcConfiguration.ps1 │ └── Set-RdcConfiguration.ps1 │ └── DSL │ ├── ADConfiguration.ps1 │ ├── RdcADComputer.ps1 │ ├── RdcADGroup.ps1 │ ├── RdcComputer.ps1 │ ├── RdcConfiguration.ps1 │ ├── RdcDocument.ps1 │ ├── RdcGroup.ps1 │ ├── RdcLogonCredential.ps1 │ └── RdcRemoteDesktopSetting.ps1 ├── build.ps1 └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | out -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Brett Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /RDCManager/InitializeModule.ps1: -------------------------------------------------------------------------------- 1 | Add-Type -AssemblyName System.Xml.Linq 2 | 3 | function InitializeModule { 4 | Set-RdcConfiguration -Reset 5 | } 6 | -------------------------------------------------------------------------------- /RDCManager/RdcManager.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'RDCManager' 3 | # 4 | # Generated by: Brett Miller & Chris Dent 5 | # 6 | # Generated on: 25/01/2019 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'RDCManager.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '2.1.3' 16 | 17 | # Supported PSEditions 18 | CompatiblePSEditions = 'Desktop', 'Core' 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '22a5df6b-7620-47de-8103-abc1869af639' 22 | 23 | # Author of this module 24 | Author = 'Brett Miller & Chris Dent' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'None' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2019 Brett Miller & Chris Dent. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'A DSL used to generate Remote Desktop Connection Manager files' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | PowerShellVersion = '5.1' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = @( 73 | 'Get-RdcConfiguration', 74 | 'Set-RdcConfiguration', 75 | 'ADConfiguration', 76 | 'RdcADComputer', 77 | 'RdcADGroup', 78 | 'RdcComputer', 79 | 'RdcConfiguration', 80 | 'RdcDocument', 81 | 'RdcGroup', 82 | 'RdcLogonCredential', 83 | 'RdcRemoteDesktopSetting' 84 | ) 85 | 86 | 87 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 88 | CmdletsToExport = @() 89 | 90 | # Variables to export from this module 91 | VariablesToExport = @() 92 | 93 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 94 | AliasesToExport = @() 95 | 96 | # DSC resources to export from this module 97 | # DscResourcesToExport = @() 98 | 99 | # List of all modules packaged with this module 100 | # ModuleList = @() 101 | 102 | # List of all files packaged with this module 103 | # FileList = @() 104 | 105 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 106 | PrivateData = @{ 107 | 108 | PSData = @{ 109 | 110 | # Tags applied to this module. These help with module discovery in online galleries. 111 | Tags = @('RDCMan', 'RDCManager', 'RemoteDesktop') 112 | 113 | # A URL to the license for this module. 114 | # LicenseUri = '' 115 | 116 | # A URL to the main website for this project. 117 | ProjectUri = 'https://github.com/brettmillerb/RDCMan' 118 | 119 | # A URL to an icon representing this module. 120 | # IconUri = '' 121 | 122 | # ReleaseNotes of this module 123 | # ReleaseNotes = '' 124 | 125 | } # End of PSData hashtable 126 | 127 | } # End of PrivateData hashtable 128 | 129 | # HelpInfo URI of this module 130 | # HelpInfoURI = '' 131 | 132 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 133 | # DefaultCommandPrefix = '' 134 | 135 | } 136 | 137 | -------------------------------------------------------------------------------- /RDCManager/RdcManager.psm1: -------------------------------------------------------------------------------- 1 | # Development root module 2 | 3 | $private = @( 4 | 'ADSI\GetAdsiComputer' 5 | 'ADSI\GetAdsiObject' 6 | 'ADSI\GetAdsiOrganizationalUnit' 7 | 'ADSI\GetAdsiRootDSE' 8 | 'ADSI\NewDirectoryEntry' 9 | 'CommandAdapter\GetADComputer' 10 | 'CommandAdapter\GetADObject' 11 | 'CommandAdapter\GetADOrganizationalUnit' 12 | ) 13 | 14 | foreach ($command in $private) { 15 | . ('{0}\private\{1}.ps1' -f $psscriptroot, $command) 16 | 17 | Split-Path $command -Leaf 18 | } 19 | 20 | $public = @( 21 | 'Configuration\Get-RdcConfiguration' 22 | 'Configuration\Set-RdcConfiguration' 23 | 'DSL\ADConfiguration' 24 | 'DSL\RdcADComputer' 25 | 'DSL\RdcADGroup' 26 | 'DSL\RdcComputer' 27 | 'DSL\RdcConfiguration' 28 | 'DSL\RdcDocument' 29 | 'DSL\RdcGroup' 30 | 'DSL\RdcLogonCredential' 31 | 'DSL\RdcRemoteDesktopSetting' 32 | ) 33 | 34 | $functionsToExport = foreach ($command in $public) { 35 | . ('{0}\public\{1}.ps1' -f $psscriptroot, $command) 36 | 37 | Split-Path $command -Leaf 38 | } 39 | 40 | . ('{0}\InitializeModule.ps1' -f $psscriptroot) 41 | InitializeModule 42 | 43 | Export-ModuleMember -Function $functionsToExport -------------------------------------------------------------------------------- /RDCManager/img/ZMRivZa5sA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettmillerb/RDCMan/bc48b25acc5d6c9102ed177bcef4e3f9bc818482/RDCManager/img/ZMRivZa5sA.png -------------------------------------------------------------------------------- /RDCManager/img/xIkQfDVql2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brettmillerb/RDCMan/bc48b25acc5d6c9102ed177bcef4e3f9bc818482/RDCManager/img/xIkQfDVql2.png -------------------------------------------------------------------------------- /RDCManager/private/ADSI/GetAdsiComputer.ps1: -------------------------------------------------------------------------------- 1 | function GetAdsiComputer { 2 | <# 3 | .SYNOPSIS 4 | Get an computer object using ADSI. 5 | .DESCRIPTION 6 | These basic ADSI commands allow the RdcMan document generator to be used without the MS AD module. 7 | 8 | Use of the internal commands is optional. If used, all filters must be written as LDAP filter. 9 | #> 10 | 11 | [CmdletBinding()] 12 | param ( 13 | # A filter describing the computers units to find. 14 | [String]$Filter, 15 | 16 | # The search base for this search. 17 | [String]$SearchBase, 18 | 19 | # The search scope for the search operation. 20 | [System.DirectoryServices.SearchScope]$SearchScope, 21 | 22 | # Limit the number of results returned by a search. By default result set size is unlimited. 23 | [Int32]$ResultSetSize, 24 | 25 | # The server to use to execute the search. 26 | [String]$Server, 27 | 28 | # Credentials to use when connecting to the server. 29 | [PSCredential]$Credential 30 | ) 31 | 32 | if ($Filter -eq '*' -or -not $Filter) { 33 | $psboundparameters['Filter'] = '(&(objectCategory=computer)(objectClass=computer))' 34 | } else { 35 | $psboundparameters['Filter'] = '(&(objectCategory=computer)(objectClass=computer){0})' -f $Filter 36 | } 37 | 38 | GetAdsiObject -Properties 'name', 'description', 'dnsHostName' @psboundparameters 39 | } -------------------------------------------------------------------------------- /RDCManager/private/ADSI/GetAdsiObject.ps1: -------------------------------------------------------------------------------- 1 | function GetAdsiObject { 2 | <# 3 | .SYNOPSIS 4 | Get an arbitrary object using ADSI. 5 | .DESCRIPTION 6 | These basic ADSI commands allow the RdcMan document generator to be used without the MS AD module. 7 | 8 | Use of the internal commands is optional. If used, all filters must be written as LDAP filter. 9 | #> 10 | 11 | [CmdletBinding()] 12 | param ( 13 | # A filter describing the computers units to find. 14 | [Parameter(Mandatory)] 15 | [String]$Filter, 16 | 17 | # A list of properties to retrieve 18 | [String[]]$Properties = 'distinguishedName', 19 | 20 | # The search base for this search. 21 | [String]$SearchBase, 22 | 23 | # The search scope for the search operation. 24 | [System.DirectoryServices.SearchScope]$SearchScope, 25 | 26 | # Limit the number of results returned by a search. By default result set size is unlimited. 27 | [Int32]$ResultSetSize, 28 | 29 | # The server to use to execute the search. 30 | [String]$Server, 31 | 32 | # Credentials to use when connecting to the server. 33 | [PSCredential]$Credential 34 | ) 35 | 36 | $params = @{} 37 | if ($Server) { $params.Add('Server', $Server) } 38 | if ($Credential) { $params.Add('Credential', $Credential) } 39 | 40 | $params = @{} 41 | if ($Server) { $params.Add('Server', $Server) } 42 | if ($Credential) { $params.Add('Credential', $Credential) } 43 | 44 | if (-not $SearchBase) { 45 | $SearchBase = (GetAdsiRootDse @params).defaultNamingContext 46 | } 47 | $adsiSearchBase = NewDirectoryEntry -DistinguishedName $SearchBase @params 48 | 49 | $searcher = [ADSISearcher]@{ 50 | Filter = $Filter 51 | SearchRoot = $adsiSearchBase 52 | SearchScope = $SearchScope 53 | PageSize = 1000 54 | } 55 | $searcher.PropertiesToLoad.AddRange($Properties) 56 | 57 | if ($ResultSetSize) { 58 | $searcher.SizeLimit = $ResultSetSize 59 | } 60 | 61 | Write-Debug 'SEARCHER:' 62 | Write-Debug (' Filter : {0}' -f $Filter) 63 | Write-Debug (' SearchBase : {0}' -f $SearchBase) 64 | Write-Debug (' SearchScope: {0}' -f $SearchScope) 65 | 66 | foreach ($searchResult in $searcher.FindAll()) { 67 | $objectProperties = @{} 68 | foreach ($property in $Properties) { 69 | $objectProperties.Add($property, $searchResult.Properties[$property][0]) 70 | } 71 | [PSCustomObject]$objectProperties 72 | } 73 | } -------------------------------------------------------------------------------- /RDCManager/private/ADSI/GetAdsiOrganizationalUnit.ps1: -------------------------------------------------------------------------------- 1 | function GetAdsiOrganizationalUnit { 2 | <# 3 | .SYNOPSIS 4 | Get an organization unit object using ADSI. 5 | .DESCRIPTION 6 | These basic ADSI commands allow the RdcMan document generator to be used without the MS AD module. 7 | 8 | Use of the internal commands is optional. If used, all filters must be written as LDAP filter. 9 | #> 10 | 11 | [CmdletBinding(DefaultParameterSetName = 'UsingFilter')] 12 | param ( 13 | # A filter describing the organizational units to find. 14 | [Parameter(ParameterSetName = 'UsingFilter')] 15 | [String]$Filter, 16 | 17 | # Use identity instead of a filter to locate the OU. 18 | [Parameter(ParameterSetName = 'ByIdentity')] 19 | [String]$Identity, 20 | 21 | # The search base for this search. 22 | [String]$SearchBase, 23 | 24 | # The search scope for the search operation. 25 | [System.DirectoryServices.SearchScope]$SearchScope, 26 | 27 | # The server to use to execute the search. 28 | [String]$Server, 29 | 30 | # Credentials to use when connecting to the server. 31 | [PSCredential]$Credential 32 | ) 33 | 34 | if ($Identity) { 35 | $psboundparameters['Filter'] = '(&(objectClass=organizationalUnit)(distinguishedName={0}))' -f $Identity 36 | $psboundparameters['SearchScope'] = 'Subtree' 37 | $null = $psboundparameters.Remove('Identity') 38 | } elseif ($Filter -eq '*' -or -not $Filter) { 39 | $psboundparameters['Filter'] = '(objectClass=organizationalUnit)' 40 | } else { 41 | $psboundparameters['Filter'] = '(&(objectClass=organizationalUnit){0})' -f $Filter 42 | } 43 | 44 | GetAdsiObject -Properties 'name', 'description', 'distinguishedName' @psboundparameters 45 | } -------------------------------------------------------------------------------- /RDCManager/private/ADSI/GetAdsiRootDSE.ps1: -------------------------------------------------------------------------------- 1 | function GetAdsiRootDse { 2 | <# 3 | .SYNOPSIS 4 | Get a RootDSE node using ADSI. 5 | .DESCRIPTION 6 | These basic ADSI commands allow the RdcMan document generator to be used without the MS AD module. 7 | 8 | Use of the internal commands is optional. If used, all filters must be written as LDAP filter. 9 | #> 10 | 11 | [CmdletBinding()] 12 | param ( 13 | # The server to use for the ADSI connection. 14 | [String]$Server, 15 | 16 | # Credentials to use when connecting to the server. 17 | [PSCredential]$Credential 18 | ) 19 | 20 | $rootDSE = NewDirectoryEntry -DistinguishedName 'RootDSE' @psboundparameters 21 | $properties = @{} 22 | foreach ($property in $rootDSE.Properties.Keys) { 23 | $properties.Add($property, $rootDSE.Properties[$property]) 24 | } 25 | [PSCustomObject]$properties 26 | } -------------------------------------------------------------------------------- /RDCManager/private/ADSI/NewDirectoryEntry.ps1: -------------------------------------------------------------------------------- 1 | function NewDirectoryEntry { 2 | <# 3 | .SYNOPSIS 4 | Creates a System.DirectoryServices.DirectoryEntry object. 5 | .DESCRIPTION 6 | Creates a System.DirectoryServices.DirectoryEntry object. 7 | #> 8 | 9 | [CmdletBinding()] 10 | param ( 11 | # The distinguished name to connect to. 12 | [String]$DistinguishedName, 13 | 14 | # The server used for the connection. 15 | [String]$Server, 16 | 17 | # Any credentials which should be used. 18 | [PSCredential]$Credential 19 | ) 20 | 21 | if ($Server) { 22 | $Path = 'LDAP://{0}/{1}' -f $Server, $DistinguishedName 23 | } else { 24 | $Path = 'LDAP://{0}' -f $DistinguishedName 25 | } 26 | if ($Credential) { 27 | [ADSI]::new($Path, $Credential.Username, $Credential.GetNetworkCredential().Password) 28 | } else { 29 | [ADSI]::new($Path) 30 | } 31 | } -------------------------------------------------------------------------------- /RDCManager/private/CommandAdapter/GetADComputer.ps1: -------------------------------------------------------------------------------- 1 | function GetADComputer { 2 | <# 3 | .SYNOPSIS 4 | Use either the ActiveDirectory module or ADSI to find computer objects. 5 | .DESCRIPTION 6 | Use either the ActiveDirectory module or ADSI to find computer objects. 7 | #> 8 | 9 | [CmdletBinding(DefaultParameterSetName = 'UsingFilter')] 10 | param ( 11 | # A filter to use for the search. If using the ActiveDirectory module this can either be an LDAP filter, or the specialised form used by the ActiveDirectory module. 12 | [Parameter(ParameterSetName = 'UsingFilter')] 13 | [String]$Filter, 14 | 15 | # When searching by name the names are assembled into a filter for each name using the OR operator. 16 | [Parameter(ParameterSetName = 'ByName')] 17 | [String[]]$Name, 18 | 19 | # A searchbase to use. If a search base is not set, the root of the current domain is used. 20 | [String]$SearchBase, 21 | 22 | # The search scope for the search operation. 23 | [System.DirectoryServices.SearchScope]$SearchScope, 24 | 25 | # Limit the number of results returned by a search. By default result set size is unlimited. 26 | [Int32]$ResultSetSize, 27 | 28 | # The server to use for the search. 29 | [String]$Server, 30 | 31 | # Credentials to use when connecting to the server. 32 | [PSCredential]$Credential, 33 | 34 | # The filter format to use. 35 | [String]$FilterFormat = (Get-RdcConfiguration -Name FilterFormat) 36 | ) 37 | 38 | $null = $psboundparameters.Remove('FilterFormat') 39 | if ($pscmdlet.ParameterSetName -eq 'ByName') { 40 | $null = $psboundparameters.Remove('Name') 41 | 42 | $FilterFormat = 'LDAP' 43 | $nameFilters = foreach ($value in $Name) { 44 | '(name={0})' -f $value 45 | } 46 | $Filter = '(|{0})' -f (-join $nameFilters) 47 | $psboundparameters.Add('Filter', $Filter) 48 | } 49 | 50 | if (Get-RdcConfiguration -Name SearchMode -Eq ADModule) { 51 | if ($FilterFormat -eq 'LDAP') { 52 | $null = $psboundparameters.Remove('Filter') 53 | $psboundparameters.Add('LdapFilter', $Filter) 54 | } 55 | Get-ADComputer -Properties dnsHostName, displayName @psboundparameters 56 | } else { 57 | GetAdsiComputer @psboundparameters 58 | } 59 | } -------------------------------------------------------------------------------- /RDCManager/private/CommandAdapter/GetADObject.ps1: -------------------------------------------------------------------------------- 1 | function GetADObject { 2 | <# 3 | .SYNOPSIS 4 | Use either the ActiveDirectory module or ADSI to find arbitrary objects. 5 | .DESCRIPTION 6 | Use either the ActiveDirectory module or ADSI to find arbitrary objects. 7 | #> 8 | 9 | [CmdletBinding()] 10 | param ( 11 | # A filter to use for the search. If using the ActiveDirectory module this can either be an LDAP filter, or the specialised form used by the ActiveDirectory module. 12 | [String]$Filter, 13 | 14 | # A searchbase to use. If a search base is not set, the root of the current domain is used. 15 | [String]$SearchBase, 16 | 17 | # The search scope for the search operation. 18 | [System.DirectoryServices.SearchScope]$SearchScope, 19 | 20 | # The server to use for the search. 21 | [String]$Server, 22 | 23 | # Credentials to use when connecting to the server. 24 | [PSCredential]$Credential 25 | ) 26 | 27 | $null = $psboundparameters.Remove('FilterFormat') 28 | if ($pscmdlet.ParameterSetName -eq 'ByName') { 29 | $null = $psboundparameters.Remove('Name') 30 | 31 | $FilterFormat = 'LDAP' 32 | $nameFilters = foreach ($value in $Name) { 33 | '(name={0})' -f $value 34 | } 35 | $Filter = '(|{0})' -f (-join $nameFilters) 36 | $psboundparameters.Add('Filter', $Filter) 37 | } 38 | 39 | if (Get-RdcConfiguration -Name SearchMode -Eq ADModule) { 40 | if ($FilterFormat -eq 'LDAP') { 41 | $null = $psboundparameters.Remove('Filter') 42 | $psboundparameters.Add('LdapFilter', $Filter) 43 | } 44 | Get-ADComputer @psboundparameters 45 | } else { 46 | GetAdsiObject @psboundparameters 47 | } 48 | } -------------------------------------------------------------------------------- /RDCManager/private/CommandAdapter/GetADOrganizationalUnit.ps1: -------------------------------------------------------------------------------- 1 | function GetADOrganizationalUnit { 2 | <# 3 | .SYNOPSIS 4 | Use either the ActiveDirectory module or ADSI to find organizational unit objects. 5 | .DESCRIPTION 6 | Use either the ActiveDirectory module or ADSI to find organizational unit objects. 7 | #> 8 | 9 | [CmdletBinding()] 10 | param ( 11 | [Parameter(ParameterSetName = 'ByName')] 12 | [String]$Name, 13 | 14 | # A filter to use for the search. If using the ActiveDirectory module this can either be an LDAP filter, or the specialised form used by the ActiveDirectory module. 15 | [Parameter(ParameterSetName = 'UsingFilter')] 16 | [String]$Filter, 17 | 18 | # Use identity instead of a filter to locate the OU. 19 | [Parameter(ParameterSetName = 'ByIdentity')] 20 | [String]$Identity, 21 | 22 | # A searchbase to use. If a search base is not set, the root of the current domain is used. 23 | [String]$SearchBase, 24 | 25 | # The search scope for the search operation. 26 | [System.DirectoryServices.SearchScope]$SearchScope, 27 | 28 | # The server to use for the search. 29 | [String]$Server, 30 | 31 | # Credentials to use when connecting to the server. 32 | [PSCredential]$Credential, 33 | 34 | # The filter format to use. 35 | [String]$FilterFormat = (Get-RdcConfiguration -Name FilterFormat) 36 | ) 37 | 38 | if ($pscmdlet.ParameterSetName -eq 'ByName') { 39 | $null = $psboundparameters.Remove('Name') 40 | 41 | $FilterFormat = 'LDAP' 42 | $Filter = '(name={0})' -f $Name 43 | $psboundparameters.Add('Filter', $Filter) 44 | } 45 | 46 | if (-not $SearchBase) { 47 | $null = $psboundparameters.Remove('SearchBase') 48 | } 49 | 50 | if (Get-RdcConfiguration -Name SearchMode -Eq ADModule) { 51 | if ($FilterFormat -eq 'LDAP') { 52 | $null = $psboundparameters.Remove('Filter') 53 | $psboundparameters.Add('LdapFilter', $Filter) 54 | } 55 | Get-ADOrganizationalUnit @psboundparameters 56 | } else { 57 | GetAdsiOrganizationalUnit @psboundparameters 58 | } 59 | } -------------------------------------------------------------------------------- /RDCManager/public/Configuration/Get-RdcConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function Get-RdcConfiguration { 2 | <# 3 | .SYNOPSIS 4 | Get the configuration for the document generator. 5 | .DESCRIPTION 6 | Get the configuration for the document generator. 7 | #> 8 | 9 | [CmdletBinding()] 10 | param ( 11 | # Get a specific configuration value by name. 12 | [String]$Name, 13 | 14 | # Get a configuration value and test whether or not it is equal to the specified value. 15 | [Object]$Eq 16 | ) 17 | 18 | if ($Name -and $psboundparameters.ContainsKey('Eq')) { 19 | $script:Configuration.$Name -eq $Eq 20 | } elseif ($Name) { 21 | $Script:configuration.$Name 22 | } else { 23 | $Script:configuration 24 | } 25 | } -------------------------------------------------------------------------------- /RDCManager/public/Configuration/Set-RdcConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function Set-RdcConfiguration { 2 | <# 3 | .SYNOPSIS 4 | Set the configuration for the document generator. 5 | .DESCRIPTION 6 | Sets the configuration used by the document generator. 7 | #> 8 | 9 | [CmdletBinding()] 10 | param ( 11 | # Set the search mode used when building content from AD. 12 | # 13 | # The following values may be set: 14 | # 15 | # - ADModule: Uses the MS ActiveDirectory module. 16 | # - ADSI: Uses the ADSI search commands in this module. 17 | # 18 | # The default search mode is ADModule if the ActiveDirectory module is available on the computer. Otherwise the search mode defaults to ADSI. 19 | # 20 | # If the ActiveDirectory module is made available using implicit remoting this option must be set. 21 | [Parameter(ParameterSetName = 'Update')] 22 | [ValidateSet('ADModule', 'ADSI')] 23 | [String]$SearchMode, 24 | 25 | # The format used for filters. By default LDAP format is used when the search mode is ADSI. The ActiveDirectory format is used if the module is used. 26 | [Parameter(ParameterSetName = 'Update')] 27 | [ValidateSet('ADModule', 'LDAP')] 28 | [String]$FilterFormat, 29 | 30 | # Reset the configuration to the default. 31 | [Parameter(ParameterSetName = 'Reset')] 32 | [Switch]$Reset 33 | ) 34 | 35 | if ($pscmdlet.ParameterSetName -eq 'Reset') { 36 | [Boolean]$isADModulePresent = Get-Module ActiveDirectory -ListAvailable 37 | 38 | $Script:configuration = [PSCustomObject]@{ 39 | SearchMode = ('ADSI', 'ADModule')[$isADModulePresent] 40 | FilterFormat = ('LDAP', 'ADModule')[$isADModulePresent] 41 | } 42 | } else { 43 | if ($SearchMode -and -not $FilterFormat) { 44 | if ($SearchMode -eq 'ADSI') { 45 | $psboundparameters['FilterFormat'] = 'LDAP' 46 | } else { 47 | $psboundparameters['FilterFormat'] = 'ADModule' 48 | } 49 | } 50 | 51 | foreach ($parameterName in $psboundparameters.Keys) { 52 | if ($Script:configuration.PSObject.Properties.Item($parameterName)) { 53 | $Script:configuration.$parameterName = $psboundparameters[$parameterName] 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/ADConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function ADConfiguration { 2 | <# 3 | .SYNOPSIS 4 | Set the AD any AD configuration which should be used when searching Active Directory. 5 | .DESCRIPTION 6 | The ADConfiguration element provides default values for AD search operations in child scopes. 7 | 8 | The ADConfiguration element is expected to be used in RdcDocument or RdcGroup elements. 9 | #> 10 | 11 | [CmdletBinding()] 12 | param ( 13 | [Parameter(Mandatory)] 14 | [ValidateScript( 15 | { 16 | if ($_.ContainsKey('Credential') -and $_['Credential'] -isnot [PSCredential]) { 17 | throw 'The credential key was present, but the value is not a credential object.' 18 | } 19 | foreach ($key in $_.Keys) { 20 | if ($key -notin 'Server', 'Credential') { 21 | throw ('Invalid key in the ADConfigurastion hashtable. Valid keys are Server and Credential') 22 | } 23 | } 24 | $true 25 | } 26 | )] 27 | [Hashtable]$ADConfiguration 28 | ) 29 | 30 | try { 31 | # Get the value of the parentNode variable from the parent scope(s) 32 | $parentNode = Get-Variable currentNode -ValueOnly -ErrorAction Stop 33 | } catch { 34 | throw ('{0} must be nested in RdcDocument or RdcGroup: {1}' -f $myinvocation.InvocationName, $_.Exception.Message) 35 | } 36 | 37 | foreach ($key in $ADConfiguration.Keys) { 38 | New-Variable -Name ('RdcAD{0}' -f $key) -Value $ADConfiguration[$key] -Scope 1 -Force 39 | } 40 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcADComputer.ps1: -------------------------------------------------------------------------------- 1 | function RdcADComputer { 2 | <# 3 | .SYNOPSIS 4 | Creates a set of computers under a document or group. 5 | .DESCRIPTION 6 | RdcADComputer is used to create computer objects based on a search of Active Directory. 7 | #> 8 | 9 | [CmdletBinding(DefaultParameterSetName = 'UsingFilter')] 10 | param ( 11 | # The filter which will be used to find computers. 12 | [Parameter(Position = 1, ParameterSetName = 'UsingFilter')] 13 | [String]$Filter = '*', 14 | 15 | # When searching by name the names are assembled into a filter for each name using the OR operator. 16 | [Parameter(ParameterSetName = 'ByName')] 17 | [String[]]$Name, 18 | 19 | # The search base. By default the search is performed from the root of the current domain. 20 | [String]$SearchBase, 21 | 22 | # The server to use for this operation. 23 | [String]$Server = (Get-Variable RdcADServer -ValueOnly -ErrorAction SilentlyContinue), 24 | 25 | # Credentials to use when connecting to active directory. 26 | [PSCredential]$Credential = (Get-Variable RdcADCredential -ValueOnly -ErrorAction SilentlyContinue), 27 | 28 | # If recurse is set computer objects from child OUs will be added to the parent group. 29 | [Switch]$Recurse 30 | ) 31 | 32 | if (-not $psboundparameters.ContainsKey('SearchBase')) { 33 | if ($candidateDN = Get-Variable parentDN -ValueOnly -ErrorAction SilentlyContinue) { 34 | $SearchBase = $candidateDN 35 | } 36 | } 37 | 38 | $params = @{ 39 | SearchBase = $SearchBase 40 | SearchScope = ('OneLevel', 'Subtree')[$Recurse.ToBool()] 41 | } 42 | if ($Name) { 43 | $params.Add('Name', $Name) 44 | } else { 45 | $params.Add('Filter', $Filter) 46 | } 47 | if ($Server) { 48 | $params.Add('Server', $Server) 49 | } 50 | if ($Credential) { 51 | $params.Add('Credential', $Credential) 52 | } 53 | 54 | GetADComputer @params | 55 | Sort-Object Name | 56 | RdcComputer 57 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcADGroup.ps1: -------------------------------------------------------------------------------- 1 | function RdcADGroup { 2 | <# 3 | .SYNOPSIS 4 | Create a group node derived from the content of an organisational unit. 5 | .DESCRIPTION 6 | Create a group node derived from the content of an organisational unit. 7 | #> 8 | 9 | [CmdletBinding(DefaultParameterSetName = 'UsingFilter')] 10 | param ( 11 | # The identity of a single OU. 12 | [Parameter(Mandatory, Position = 1, ParameterSetName = 'ByName')] 13 | [String]$Name, 14 | 15 | # A filter for OU objects. 16 | [Parameter(ParameterSetName = 'UsingFilter')] 17 | [String]$Filter = '*', 18 | 19 | # The identity of a single OU. 20 | [Parameter(Mandatory, ParameterSetName = 'ByIdentity')] 21 | [String]$Identity, 22 | 23 | # A filter to apply when evaluating descendent computer objects. 24 | [String]$ComputerFilter = '*', 25 | 26 | # The search base to use when using a filter. 27 | [Parameter(ParameterSetName = 'UsingFilter')] 28 | [String]$SearchBase, 29 | 30 | # The server to use for this operation. 31 | [String]$Server = (Get-Variable RdcADServer -ValueOnly -ErrorAction SilentlyContinue), 32 | 33 | # Credentials to use when connecting to active directory. 34 | [PSCredential]$Credential = (Get-Variable RdcADCredential -ValueOnly -ErrorAction SilentlyContinue), 35 | 36 | # If Recurse is set, groups will be created in the RDC document reprsenting each child organisational unit. 37 | # 38 | # Organizational units are only included as groups if the oganizational unit contains computer accounts or other organizational units. 39 | [Switch]$Recurse 40 | ) 41 | 42 | if (-not $psboundparameters.ContainsKey('SearchBase')) { 43 | if ($candidateDN = Get-Variable parentDN -ValueOnly -ErrorAction SilentlyContinue) { 44 | $SearchBase = $candidateDN 45 | } 46 | } 47 | 48 | if ($pscmdlet.ParameterSetName -eq 'ByIdentity') { 49 | $params = @{ 50 | Identity = $Identity 51 | } 52 | } elseif ($pscmdlet.ParameterSetName -eq 'ByName') { 53 | $params = @{ 54 | Name = $Name 55 | SearchBase = $SearchBase 56 | SearchScope = 'Subtree' 57 | } 58 | } else { 59 | $params = @{ 60 | Filter = $Filter 61 | SearchBase = $SearchBase 62 | SearchScope = 'OneLevel' 63 | } 64 | } 65 | 66 | $serverAndCredential = @{} 67 | if ($Server) { 68 | $serverAndCredential.Add('Server', $Server) 69 | } 70 | if ($Credential) { 71 | $serverAndCredential.Add('Credential', $Credential) 72 | } 73 | 74 | GetADOrganizationalUnit @params @serverAndCredential | ForEach-Object { 75 | # Determine if the OU has child objects. If so, allow it to be included. 76 | Write-Debug 'Searching for child computer objects' 77 | Write-Debug (' SearchBase: {0}' -f $_.DistinguishedName) 78 | 79 | $params = @{ 80 | Filter = '*' 81 | SearchBase = $_.DistinguishedName 82 | SearchScope = 'Subtree' 83 | ResultSetSize = 1 84 | } 85 | if (GetADComputer @params @serverAndCredential) { 86 | Write-Verbose ('Creating group {0}' -f $_.Name) 87 | 88 | $parentDN = $_.DistinguishedName 89 | if ($Recurse) { 90 | RdcGroup $_.Name { 91 | RdcADGroup -Recurse -ComputerFilter $ComputerFilter @serverAndCredential 92 | RdcADComputer -Filter $ComputerFilter @serverAndCredential 93 | } 94 | } else { 95 | RdcGroup $_.Name { 96 | RdcADComputer -Filter $ComputerFilter @serverAndCredential -Recurse 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcComputer.ps1: -------------------------------------------------------------------------------- 1 | function RdcComputer { 2 | <# 3 | .SYNOPSIS 4 | Create a computer in the RDCMan document. 5 | .DESCRIPTION 6 | Create a computer in the RDCMan document. 7 | #> 8 | 9 | [CmdletBinding(DefaultParameterSetName = 'FromPipeline')] 10 | param ( 11 | [Parameter(Mandatory, ValueFromPipelineByPropertyName, ParameterSetName = 'FromPipeline')] 12 | [String]$Name, 13 | 14 | [Parameter(Position = 2, ValueFromPipelineByPropertyName, ParameterSetName = 'FromPipeline')] 15 | [String]$DnsHostName, 16 | 17 | [Parameter(Position = 3, ValueFromPipelineByPropertyName, ParameterSetName = 'FromPipeline')] 18 | [Alias('IPv4Address')] 19 | [String]$Comment, 20 | 21 | [Parameter(Mandatory, Position = 1, ParameterSetName = 'FromHashtable')] 22 | [ValidateScript( 23 | { 24 | if (-not $_.ContainsKey('Name')) { 25 | throw 'The Name key must be present' 26 | } 27 | foreach ($key in $_.Keys) { 28 | if ($key -notin 'Name', 'DnsHostName', 'Comment') { 29 | throw ('Invalid key in Properties hashtable. Valid keys are Name, DnsHostName, and Comment') 30 | } 31 | } 32 | $true 33 | } 34 | )] 35 | [Hashtable]$Properties 36 | ) 37 | 38 | begin { 39 | try { 40 | # Get the value of the parentNode variable from the parent scope(s) 41 | $parentNode = Get-Variable currentNode -ValueOnly -ErrorAction Stop 42 | } catch { 43 | throw ('{0} must be nested in RdcDocument or RdcGroup: {1}' -f $myinvocation.InvocationName, $_.Exception.Message) 44 | } 45 | } 46 | 47 | process { 48 | if ($Properties) { 49 | $Name = $Properties.Name 50 | $DnsHostName = $Properties.DnsHostName 51 | $Comment = $Properties.Comment 52 | } 53 | if (-not $DnsHostName) { 54 | $DnsHostName = $Name 55 | } 56 | 57 | $xElement = [System.Xml.Linq.XElement](' 58 | 59 | 60 | {0} 61 | {1} 62 | {2} 63 | 64 | ' -f $Name, $DnsHostName, $Comment) 65 | 66 | if ($parentNode -is [System.Xml.Linq.XDocument]) { 67 | $parentNode.Element('Rdc').Element('connected').AddBeforeSelf($xElement) 68 | } else { 69 | $parentNode.Element('properties').AddAfterSelf($xElement) 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function RdcConfiguration { 2 | <# 3 | .SYNOPSIS 4 | 5 | .DESCRIPTION 6 | RdcConfiguration allows the generator behaviours to be defined using a node in the document. 7 | .EXAMPLE 8 | RdcDocument name { 9 | RdcConfiguration @{ 10 | SearchMode = 'ADSI' 11 | } 12 | } 13 | #> 14 | 15 | [CmdletBinding()] 16 | param ( 17 | [Parameter(Mandatory)] 18 | [Hashtable]$Configuration 19 | ) 20 | 21 | Set-RdcConfiguration @Configuration 22 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcDocument.ps1: -------------------------------------------------------------------------------- 1 | function RdcDocument { 2 | <# 3 | .SYNOPSIS 4 | Declare an RDCMan document. 5 | .DESCRIPTION 6 | An RDC Document defines the basic document content and is the starting point for creating groups and computer elements. 7 | #> 8 | 9 | [CmdletBinding()] 10 | param ( 11 | # The path to a file to save content. 12 | [Parameter(Mandatory, Position = 1)] 13 | [Alias('FileName', 'FullName')] 14 | [String]$Path, 15 | 16 | # A script block defining the content of the document. 17 | [Parameter(Mandatory, Position = 2)] 18 | [ScriptBlock]$Children 19 | ) 20 | 21 | $xDocument = $currentNode = [System.Xml.Linq.XDocument]::Parse(' 22 | 23 | 24 | 25 | 26 | 27 | {0} 28 | 29 | 30 | 31 | 32 | 33 | '.Trim() -f ([System.IO.FileInfo]$Path).BaseName) 34 | 35 | if ($Children) { 36 | & $Children 37 | } 38 | 39 | if ($Path -notmatch '\.rdg$') { 40 | $Path = '{0}.rdg' -f $Path 41 | } 42 | $Path = $pscmdlet.GetUnresolvedProviderPathFromPSPath($Path) 43 | $xDocument.Save($Path) 44 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcGroup.ps1: -------------------------------------------------------------------------------- 1 | function RdcGroup { 2 | [CmdletBinding()] 3 | param ( 4 | [Parameter(Mandatory, ValueFromPipeline, Position = 1)] 5 | [String]$Name, 6 | 7 | [Parameter(Mandatory, Position = 2)] 8 | [ScriptBlock]$Children 9 | ) 10 | 11 | try { 12 | # Get the value of the parentNode variable from the parent scope(s) 13 | $parentNode = Get-Variable currentNode -ValueOnly -ErrorAction Stop 14 | } catch { 15 | throw ('{0} must be nested in RdcDocument or RdcGroup: {1}' -f $myinvocation.InvocationName, $_.Exception.Message) 16 | } 17 | 18 | $xElement = $currentNode = [System.Xml.Linq.XElement]::new('group', 19 | [System.Xml.Linq.XElement]::new('properties', 20 | [System.Xml.Linq.XElement]::new('name', $Name) 21 | ) 22 | ) 23 | 24 | if ($parentNode -is [System.Xml.Linq.XDocument]) { 25 | $parentNode.Element('Rdc').Element('file').Add($xElement) 26 | } else { 27 | $parentNode.Add($xElement) 28 | } 29 | 30 | if ($Children) { 31 | & $Children 32 | } 33 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcLogonCredential.ps1: -------------------------------------------------------------------------------- 1 | function RdcLogonCredential { 2 | <# 3 | .SYNOPSIS 4 | Creates a node to save credentials in the parent group or document. 5 | .DESCRIPTION 6 | Creates a node to save credentials in the parent group or document. 7 | #> 8 | 9 | [CmdletBinding(DefaultParameterSetName = 'FromHashtable')] 10 | param ( 11 | [Parameter(Position = 1, ParameterSetName = 'FromHashtable')] 12 | [ValidateScript( 13 | { 14 | if ($_.Contains('Password') -and $_['Password'] -isnot [SecureString]) { 15 | throw 'Passwords must be stored as a secure string' 16 | } 17 | foreach ($key in $_.Keys) { 18 | if ($key -notin 'Username', 'Password', 'Domain') { 19 | throw ('Invalid key in the RdcLogonCredentials hashtable. Valid keys are UserName, Password, and Domain') 20 | } 21 | } 22 | $true 23 | } 24 | )] 25 | [Hashtable]$CredentialHash, 26 | 27 | [Parameter(ParameterSetName = 'FromCredential')] 28 | [PSCredential]$Credential, 29 | 30 | [Switch]$SavePassword 31 | ) 32 | 33 | try { 34 | # Get the value of the parentNode variable from the parent scope(s) 35 | $parentNode = Get-Variable currentNode -ValueOnly -ErrorAction Stop 36 | } catch { 37 | throw ('{0} must be nested in RdcDocument or RdcGroup: {1}' -f $myinvocation.InvocationName, $_.Exception.Message) 38 | } 39 | 40 | if ($Credential) { 41 | if ($Credential.Username.Contains('\')) { 42 | $domainName, $username = $Credential.UserName -split '\\', 2 43 | } else { 44 | $domainName = '' 45 | $userName = $Credential.UserName 46 | } 47 | $secureString = $Credential.Password 48 | } else { 49 | $domainName = $CredentialHash['Domain'] 50 | $userName = $CredentialHash['UserName'] 51 | $secureString = $CredentialHash['Password'] 52 | } 53 | 54 | if ($secureString.Length -gt 0) { 55 | $encryptedHexString = $secureString | ConvertFrom-SecureString 56 | $bytes = for ($i = 0; $i -lt $encryptedHexString.Length; $i += 2) { 57 | [Convert]::ToByte( 58 | ('{0}{1}' -f $encryptedHexString[$i], $encryptedHexString[$i + 1]), 59 | 16 60 | ) 61 | } 62 | $encryptedPassword = [Convert]::ToBase64String($bytes) 63 | } else { 64 | $encryptedPassword = '' 65 | } 66 | 67 | # V2: BigInteger variation 68 | 69 | # Add-Type -AssemblyName System.Numerics 70 | # $bytes = [System.Numerics.BigInteger]::Parse( 71 | # ($secureString | ConvertFrom-SecureString), 72 | # 'HexNumber' 73 | # ).ToByteArray() 74 | # [Array]::Reverse($bytes) 75 | 76 | # $encryptedString = [Convert]::ToBase64String($bytes) 77 | 78 | # [RdcMan.Encryption]::DecryptString($encryptedString, [RdcMan.EncryptionSettings]::new()) 79 | 80 | # V3: Decrypt and reencrypt 81 | 82 | # $encryptedString = [Convert]::ToBase64String( 83 | # [System.Security.Cryptography.ProtectedData]::Protect( 84 | # [System.Text.Encoding]::Unicode.GetBytes( 85 | # $Credential.GetNetworkCredential().Password 86 | # ), 87 | # $null, 88 | # 'CurrentUser' 89 | # ) 90 | # ) 91 | 92 | $xElement = [System.Xml.Linq.XElement](' 93 | 94 | Custom 95 | {0} 96 | {1} 97 | {2} 98 | ' -f $username, $encryptedPassword, $domainName) 99 | 100 | if ($parentNode.NodeType -eq 'Document') { 101 | $propertiesElement = $parentNode.FirstNode.Element('file').Element('properties') 102 | } 103 | else { 104 | $propertiesElement = $parentNode.Element('properties') 105 | } 106 | $propertiesElement.AddAfterSelf($xElement) 107 | } -------------------------------------------------------------------------------- /RDCManager/public/DSL/RdcRemoteDesktopSetting.ps1: -------------------------------------------------------------------------------- 1 | function RdcRemoteDesktopSetting { 2 | <# 3 | .SYNOPSIS 4 | Creates a node to configure remote desktop settings in the parent group or document. 5 | .DESCRIPTION 6 | Creates a node to configure remote desktop settings in the parent group or document. 7 | #> 8 | 9 | [CmdletBinding(DefaultParameterSetName = 'FromHashtable')] 10 | param ( 11 | # Remote Destkop Settings configuration. 12 | # 13 | # Remote destkop settings allows the following to be defined: 14 | # 15 | # - Size - A value in the form Horizontal x Vertical. 16 | # - SameSizeAsClientArea - True or False. Make the remote desktop area fill the client window pane. 17 | # - FullScreen - True or False. Make the remote desktop full screen. 18 | # - ColorDepth - By default 24. ColorDepth can be set to 8, 15, 16, 24, or 32. 19 | [Parameter(Position = 1, ParameterSetName = 'FromHashtable')] 20 | [ValidateScript( 21 | { 22 | foreach ($key in $_.Keys) { 23 | if ($key -notin 'Size', 'SameSizeAsClientArea', 'FullScreen', 'ColorDepth') { 24 | throw ('Invalid key in the RdcLogonCredentials hashtable. Valid keys are Size, SameSizeAsClientArea, FullScreen, and ColorDepth') 25 | } 26 | } 27 | $true 28 | } 29 | )] 30 | [Hashtable]$SettingsHash 31 | ) 32 | 33 | try { 34 | # Get the value of the parentNode variable from the parent scope(s) 35 | $parentNode = Get-Variable currentNode -ValueOnly -ErrorAction Stop 36 | } catch { 37 | throw ('{0} must be nested in RdcDocument or RdcGroup: {1}' -f $myinvocation.InvocationName, $_.Exception.Message) 38 | } 39 | 40 | $settings = @{ 41 | Size = $null 42 | SameSizeAsClientArea = $false 43 | FullScreen = $true 44 | ColorDepth = 24 45 | } 46 | foreach ($setting in $SettingsHash.Keys) { 47 | $settings[$setting] = $settingsHash[$setting] 48 | } 49 | if ($SettingsHash.Contains('SameSizeAsClientArea') -and $SettingsHash['SameSizeAsClientArea']) { 50 | $settings['FullScreen'] = $false 51 | } 52 | 53 | if ($settings['ColorDepth'] -notin 8, 15, 16, 24, 32) { 54 | throw 'Invalid color depth. Valid values are 8, 15, 16, 24, and 32.' 55 | } 56 | if ($settings['Size'] -and $settings['Size'] -notmatch '^\d+ *x *\d+$') { 57 | throw 'Invalid desktop size. Sizes must be specified in the format "Horizontal x Vertical"' 58 | } elseif ($settings['Size'] -match '^(\d+) *x *(\d+)$') { 59 | # Ensure Size is formatted exactly as RdcMan expects it to be. 60 | $settings['Size'] = '{0} x {1}' -f $matches[1], $matches[2] 61 | } 62 | 63 | $xElement = [System.Xml.Linq.XElement](' 64 | 65 | {0} 66 | {1} 67 | {2} 68 | ' -f $settings['SameSizeAsClientArea'], $settings['FullScreen'], $settings['ColorDepth']) 69 | 70 | if (-not $settings['FullScreen'] -and $settings['Size']) { 71 | $null = $xElement.Element('remoteDestkop').AddFirst( 72 | [System.Xml.Linq.XElement]('{0}' -f $settings['Size']) 73 | ) 74 | } 75 | 76 | if ($parentNode -is [System.Xml.Linq.XDocument]) { 77 | $parentNode.Element('Rdc').Element('file').Add($xElement) 78 | } else { 79 | $parentNode.Add($xElement) 80 | } 81 | } -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param ( 3 | $SourceFolder = "$PSScriptRoot", 4 | $Destination = "$PSScriptRoot\out" 5 | ) 6 | 7 | function Get-FilesToMerge { 8 | [cmdletbinding()] 9 | param ( 10 | $Path = $PSScriptRoot 11 | ) 12 | 13 | Get-ChildItem -Path $PSScriptRoot -Recurse -Include *.ps1 -Exclude build.ps1 | 14 | Sort-Object FullName | ForEach-Object { 15 | "Adding {0} to the psm1 file" -f $_.BaseName | Write-Verbose 16 | Get-Content -Path $_.FullName | Add-Content -Path $PSScriptRoot\out\RDCManager\RDCManager.psm1 17 | "`r" | Add-Content -Path $PSScriptRoot\out\RDCManager\RDCManager.psm1 18 | } 19 | } 20 | 21 | # Remove out directory to allow rebuilding 22 | if (Test-Path -Path $PSScriptRoot\out) { 23 | Remove-Item -Path $Destination -Force -Confirm:$false -Recurse 24 | } 25 | 26 | $null = New-Item -ItemType Directory -Name RDCManager -Path $PSScriptRoot\out 27 | 28 | Get-FilesToMerge 29 | 30 | "Adding InitializeModule to the psm1 file" | Write-Verbose 31 | "InitializeModule" | Add-Content -Path $PSScriptRoot\out\RDCManager\RDCManager.psm1 32 | 33 | "Copying Manifest file to {0}" -f $Destination | Write-Verbose 34 | Copy-Item -Path $PSScriptRoot\RDCManager\*.psd1 -Destination $PSScriptRoot\out\RDCManager 35 | 36 | #$content -replace "^FunctionsToExport = '[*]'$", ("FunctionsToExport = @('{0}'`r`n)" -f ($pubfunctions -join "',`r`n`t'")) 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![PowerShell Gallery](https://img.shields.io/powershellgallery/v/RdcManager?style=flat-square) [![Build Status](https://dev.azure.com/brettmillerb/RDCMan/_apis/build/status/RDCMan%20Build?branchName=master)](https://dev.azure.com/brettmillerb/RDCMan/_build/latest?definitionId=1&branchName=master) 2 | 3 | # Remote Desktop Manager File Generator 4 | 5 | ~~Idea taken from Kevin Marquette's RDCMan DSL to generate the entire XML for a new file.~~ 6 | 7 | Now **very** loosely based on Kevin Marquette's RDCMan DSL blog post 8 | 9 | https://kevinmarquette.github.io/2017-03-04-Powershell-DSL-example-RDCMan/ 10 | 11 | Entirely reworked by [Chris Dent - Indented Automation](https://github.com/indented-automation) 12 | 13 | ## Usage 14 | RDCManager alllows you to group servers and store configuration data so you can effectively manage your server estate from one window. 15 | 16 | This module allows you to dynamically create the document for RDCManager by extracting the data from Active Directory. This saves you having to remember to add/remove servers as they change in your estate. 17 | 18 | The module offers the option of using the Active Directory module if RSAT tools are installed otherwise you can specify to use LDAP to perform queries. 19 | 20 | ## Generating the document 21 | 22 | ### RdcDocument 23 | This must be specified as this is the starting point for you to begin defining groups/computers. 24 | 25 | You can then define your document manually with the `RdcGroup` and `RdcComputer` functions. 26 | 27 | ```powershell 28 | RdcDocument MyServers { 29 | RdcGroup "My First Group" { 30 | RdcComputer -Name 'server001' -DnsHostName 'server001.fqdn.com' -IPv4address '10.0.0.1' 31 | RdcComputer -Name 'server002' -DnsHostName 'server002.fqdn.com' -IPv4address '10.0.0.2' 32 | } 33 | } 34 | ``` 35 | #### Output 36 | ![RDCManBasicOutput](/RDCManager/img/ZMRivZa5sA.png) 37 | 38 | ### Generating a file with specific Active Directory filtering 39 | ```powershell 40 | RdcDocument MyServers { 41 | RdcGroup "My Group" { 42 | # Will create a group with the OU name and any computer objects within 43 | RdcADGroup -Identity 'OU=Newcastle,DC=millerb,DC=co,DC=uk' 44 | 45 | #Add -Recurse switch and it will create a group for each sub OU and add members accordingly 46 | RdcADGroup -Identity 'OU=London,DC=millerb,DC=co,DC=uk' -Recurse 47 | 48 | # Will search for a specific server provided by the filter recursing all OU's 49 | RdcADComputer -Name 'Admin*' -Recurse 50 | } 51 | } 52 | ``` 53 | 54 | ### Automatic creation from root of domain 55 | This will create groups replicating the OU structure which contains computer objects. 56 | 57 | ```powershell 58 | RdcDocument 'Domain' { 59 | RdcADGroup -Recurse 60 | } 61 | ``` 62 | 63 | #### Output 64 | ![RDCManOutput](/RDCManager/img/xIkQfDVql2.png) 65 | 66 | ## Additional Functionality 67 | 68 | #### RdcConfiguration 69 | Sets the configuration to be used when generating the document. This is not required as if the AD module is detected this will be used unless ADSI is specified. If the AD Module is not available then ADSI LDAP filters will be used. 70 | 71 | ```powershell 72 | RdcConfiguration @{ 73 | SearchMode = 'ADSI' 74 | FilterFormat = 'LDAP' 75 | } 76 | ``` 77 | 78 | #### RdcLogonCredential 79 | Allows credentials to be set at any level to support different domains/forests. May conflict with GPO's set to prevent saved passwords being used. 80 | 81 | ```powershell 82 | RdcLogonCredential @{ 83 | Username = 'millerb-admin' 84 | Domain = 'millerb.co.uk' 85 | } 86 | ``` 87 | #### RdcRemoteDesktopSetting 88 | Enables scaling of the connected client window. 89 | ```powershell 90 | RdcRemoteDesktopSetting @{ 91 | SameSizeAsClientArea = $true 92 | } 93 | ``` --------------------------------------------------------------------------------