├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── validate.yml ├── .gitignore ├── .vscode ├── help.code-snippets └── object.code-snippets ├── DCManagement ├── DCManagement.psd1 ├── DCManagement.psm1 ├── bin │ ├── DCManagement.dll │ ├── DCManagement.pdb │ ├── DCManagement.xml │ └── readme.md ├── changelog.md ├── en-us │ ├── about_DCManagement.help.txt │ └── strings.psd1 ├── functions │ ├── AccessRules │ │ ├── Get-DCAccessRule.ps1 │ │ ├── Invoke-DCAccessRule.ps1 │ │ ├── Register-DCAccessRule.ps1 │ │ ├── Test-DCAccessRule.ps1 │ │ └── Unregister-DCAccessRule.ps1 │ ├── deployment │ │ ├── Install-DCChildDomain.ps1 │ │ ├── Install-DCDomainController.ps1 │ │ └── Install-DCRootDomain.ps1 │ ├── readme.md │ ├── shares │ │ ├── Get-DCShare.ps1 │ │ ├── Invoke-DCShare.ps1 │ │ ├── Register-DCShare.ps1 │ │ ├── Test-DCShare.ps1 │ │ └── Unregister-DCShare.ps1 │ └── system │ │ ├── Clear-DCConfiguration.ps1 │ │ └── Set-DCDomainContext.ps1 ├── internal │ ├── configurations │ │ ├── configuration.ps1 │ │ └── readme.md │ ├── functions │ │ ├── Assert-ADConnection.ps1 │ │ ├── Assert-Configuration.ps1 │ │ ├── Compare-Property.ps1 │ │ ├── Get-DomainController.ps1 │ │ ├── Grant-ShareAccess.ps1 │ │ ├── New-Password.ps1 │ │ ├── New-Testresult.ps1 │ │ ├── Resolve-ParameterValue.ps1 │ │ ├── Revoke-ShareAccess.ps1 │ │ └── readme.md │ ├── scriptblocks │ │ └── scriptblocks.ps1 │ ├── scripts │ │ ├── initialize.ps1 │ │ ├── license.ps1 │ │ ├── postimport.ps1 │ │ ├── preimport.ps1 │ │ ├── strings.ps1 │ │ └── variables.ps1 │ └── tepp │ │ ├── assignment.ps1 │ │ ├── example.tepp.ps1 │ │ └── readme.md ├── pending │ └── registrysetting │ │ ├── Get-DCRegistrySetting.ps1 │ │ ├── Invoke-DCRegistrySetting.ps1 │ │ ├── Register-DCRegistrySetting.ps1 │ │ ├── Test-DCRegistrySetting.ps1 │ │ └── Unregister-DCRegistrySetting.ps1 ├── readme.md ├── snippets │ ├── help_par_ContextName.snippet │ └── help_par_Server_Credential.snippet ├── tests │ ├── functions │ │ └── readme.md │ ├── general │ │ ├── FileIntegrity.Exceptions.ps1 │ │ ├── FileIntegrity.Tests.ps1 │ │ ├── Help.Exceptions.ps1 │ │ ├── Help.Tests.ps1 │ │ ├── Manifest.Tests.ps1 │ │ ├── PSScriptAnalyzer.Tests.ps1 │ │ ├── strings.Exceptions.ps1 │ │ └── strings.Tests.ps1 │ ├── pester.ps1 │ └── readme.md └── xml │ ├── DCManagement.Format.ps1xml │ ├── DCManagement.Types.ps1xml │ └── readme.md ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── build ├── AzureFunction.readme.md ├── filesAfter.txt ├── filesBefore.txt ├── vsts-build.ps1 ├── vsts-createFunctionClientModule.ps1 ├── vsts-packageFunction.ps1 ├── vsts-prerequisites.ps1 └── vsts-validate.ps1 ├── install.ps1 └── library └── DCManagement ├── DCManagement.sln └── DCManagement ├── DCManagement.csproj └── FileSystemPermission.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | FriedrichWeinmann 5 | patreon: # Replace with a single Patreon username 6 | open_collective: # Replace with a single Open Collective username 7 | ko_fi: # Replace with a single Ko-fi username 8 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | liberapay: # Replace with a single Liberapay username 11 | issuehunt: # Replace with a single IssueHunt username 12 | otechie: # Replace with a single Otechie username 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | jobs: 7 | build: 8 | 9 | runs-on: windows-2019 10 | 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Install Prerequisites 14 | run: .\build\vsts-prerequisites.ps1 15 | shell: powershell 16 | - name: Validate 17 | run: .\build\vsts-validate.ps1 18 | shell: powershell 19 | - name: Build 20 | run: .\build\vsts-build.ps1 -ApiKey $env:APIKEY 21 | shell: powershell 22 | env: 23 | APIKEY: ${{ secrets.ApiKey }} 24 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | on: [pull_request] 2 | 3 | jobs: 4 | validate: 5 | 6 | runs-on: windows-2019 7 | 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Install Prerequisites 11 | run: .\build\vsts-prerequisites.ps1 12 | shell: powershell 13 | - name: Validate 14 | run: .\build\vsts-validate.ps1 15 | shell: powershell 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 |  2 | # ignore the settings folder and files for VSCode and PSS 3 | *.psproj 4 | *TempPoint* 5 | 6 | # Ignore staging info from Visual Studio 7 | library/DCManagement/.vs/* 8 | library/DCManagement/DCManagement/bin/* 9 | library/DCManagement/DCManagement/obj/* 10 | 11 | # ignore PowerShell Studio MetaData 12 | DCManagement/DCManagement.psproj 13 | DCManagement/DCManagement.psproj.bak 14 | DCManagement/DCManagement.psprojs 15 | DCManagement/DCManagement.psproj 16 | 17 | # ignore the TestResults 18 | TestResults/* 19 | 20 | # ignore the publishing Directory 21 | publish/* -------------------------------------------------------------------------------- /.vscode/help.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Server Help": { 3 | "scope": "powershell", 4 | "prefix": "PARAMETER Server", 5 | "body": [ 6 | "PARAMETER Server", 7 | "\tThe server / domain to work with.", 8 | "", 9 | ".PARAMETER Credential", 10 | "\tThe credentials to use for this operation." 11 | ], 12 | "description": "Inserts CBH for Server & Credential" 13 | }, 14 | "WhatIf Help": { 15 | "scope": "powershell", 16 | "prefix": "PARAMETER Confirm", 17 | "body": [ 18 | "PARAMETER Confirm", 19 | "\tIf this switch is enabled, you will be prompted for confirmation before executing any operations that change state.", 20 | "", 21 | ".PARAMETER WhatIf", 22 | "\tIf this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run." 23 | ], 24 | "description": "Inserts CBH for WhatIf & Confirm" 25 | }, 26 | "Enable Exception Help": { 27 | "scope": "powershell", 28 | "prefix": "PARAMETER EnableException", 29 | "body": [ 30 | "PARAMETER EnableException", 31 | "\tThis parameters disables user-friendly warnings and enables the throwing of exceptions.", 32 | "\tThis is less user friendly, but allows catching exceptions in calling scripts." 33 | ], 34 | "description": "Inserts CBH for EnableException" 35 | } 36 | // Place your ForestManagement workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and 37 | // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope 38 | // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is 39 | // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: 40 | // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. 41 | // Placeholders with the same ids are connected. 42 | // Example: 43 | // "Print to console": { 44 | // "scope": "javascript,typescript", 45 | // "prefix": "log", 46 | // "body": [ 47 | // "console.log('$1');", 48 | // "$2" 49 | // ], 50 | // "description": "Log output to console" 51 | // } 52 | } -------------------------------------------------------------------------------- /.vscode/object.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "PSCustomObject": { 3 | "scope": "powershell", 4 | "prefix": "[PSCustomObject]", 5 | "body": [ 6 | "[PSCustomObject]@{", 7 | "\t", 8 | "}" 9 | ], 10 | "description": "Fixes that friggin' PSCustomObject snippet the PS Team inflicted on us" 11 | } 12 | } -------------------------------------------------------------------------------- /DCManagement/DCManagement.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | # Script module or binary module file associated with this manifest 3 | RootModule = 'DCManagement.psm1' 4 | 5 | # Version number of this module. 6 | ModuleVersion = '1.2.25' 7 | 8 | # ID used to uniquely identify this module 9 | GUID = '998b2262-9b38-4b54-8ce6-493a00d70b03' 10 | 11 | # Author of this module 12 | Author = 'Friedrich Weinmann' 13 | 14 | # Company or vendor of this module 15 | CompanyName = 'Microsoft' 16 | 17 | # Copyright statement for this module 18 | Copyright = 'Copyright (c) 2019 Friedrich Weinmann' 19 | 20 | # Description of the functionality provided by this module 21 | Description = 'Manage Domain Controller Configurations' 22 | 23 | # Minimum version of the Windows PowerShell engine required by this module 24 | PowerShellVersion = '5.0' 25 | 26 | # Modules that must be imported into the global environment prior to importing 27 | # this module 28 | RequiredModules = @( 29 | @{ ModuleName = 'PSFramework'; ModuleVersion = '1.6.198' } 30 | 31 | # Additional Dependencies, cannot declare due to bug in dependency handling in PS5.1 32 | # @{ ModuleName = 'ResolveString'; ModuleVersion = '1.0.0' } 33 | # @{ ModuleName = 'Principal'; ModuleVersion = '1.0.0' } 34 | # @{ ModuleName = 'ADMF.Core'; ModuleVersion = '1.0.0' } 35 | ) 36 | 37 | # Assemblies that must be loaded prior to importing this module 38 | RequiredAssemblies = @('bin\DCManagement.dll') 39 | 40 | # Type files (.ps1xml) to be loaded when importing this module 41 | # TypesToProcess = @('xml\DCManagement.Types.ps1xml') 42 | 43 | # Format files (.ps1xml) to be loaded when importing this module 44 | FormatsToProcess = @('xml\DCManagement.Format.ps1xml') 45 | 46 | # Functions to export from this module 47 | FunctionsToExport = @( 48 | 'Clear-DCConfiguration' 49 | 'Get-DCAccessRule' 50 | 'Get-DCShare' 51 | 'Install-DCChildDomain' 52 | 'Install-DCDomainController' 53 | 'Install-DCRootDomain' 54 | 'Invoke-DCAccessRule' 55 | 'Invoke-DCShare' 56 | 'Register-DCAccessRule' 57 | 'Register-DCShare' 58 | 'Set-DCDomainContext' 59 | 'Test-DCAccessRule' 60 | 'Test-DCShare' 61 | 'Unregister-DCAccessRule' 62 | 'Unregister-DCShare' 63 | ) 64 | 65 | # Cmdlets to export from this module 66 | # CmdletsToExport = '' 67 | 68 | # Variables to export from this module 69 | # VariablesToExport = '' 70 | 71 | # Aliases to export from this module 72 | # AliasesToExport = '' 73 | 74 | # List of all modules packaged with this module 75 | # ModuleList = @() 76 | 77 | # List of all files packaged with this module 78 | # FileList = @() 79 | 80 | # Private data to pass to the module specified in ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 81 | PrivateData = @{ 82 | 83 | #Support for PowerShellGet galleries. 84 | PSData = @{ 85 | 86 | # Tags applied to this module. These help with module discovery in online galleries. 87 | Tags = @('activedirectory','dc','admf') 88 | 89 | # A URL to the license for this module. 90 | LicenseUri = 'https://github.com/ActiveDirectoryManagementFramework/DCManagement/blob/master/LICENSE' 91 | 92 | # A URL to the main website for this project. 93 | ProjectUri = 'https://admf.one' 94 | 95 | # A URL to an icon representing this module. 96 | # IconUri = '' 97 | 98 | # ReleaseNotes of this module 99 | ReleaseNotes = 'https://github.com/ActiveDirectoryManagementFramework/DCManagement/blob/master/DCManagement/changelog.md' 100 | 101 | } # End of PSData hashtable 102 | 103 | } # End of PrivateData hashtable 104 | } 105 | -------------------------------------------------------------------------------- /DCManagement/DCManagement.psm1: -------------------------------------------------------------------------------- 1 | $script:ModuleRoot = $PSScriptRoot 2 | $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\DCManagement.psd1").ModuleVersion 3 | 4 | # Detect whether at some level dotsourcing was enforced 5 | $script:doDotSource = Get-PSFConfigValue -FullName DCManagement.Import.DoDotSource -Fallback $false 6 | if ($DCManagement_dotsourcemodule) { $script:doDotSource = $true } 7 | 8 | <# 9 | Note on Resolve-Path: 10 | All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. 11 | This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. 12 | Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. 13 | This is important when testing for paths. 14 | #> 15 | 16 | # Detect whether at some level loading individual module files, rather than the compiled module was enforced 17 | $importIndividualFiles = Get-PSFConfigValue -FullName DCManagement.Import.IndividualFiles -Fallback $false 18 | if ($DCManagement_importIndividualFiles) { $importIndividualFiles = $true } 19 | if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } 20 | if ("" -eq '') { $importIndividualFiles = $true } 21 | 22 | function Import-ModuleFile 23 | { 24 | <# 25 | .SYNOPSIS 26 | Loads files into the module on module import. 27 | 28 | .DESCRIPTION 29 | This helper function is used during module initialization. 30 | It should always be dotsourced itself, in order to proper function. 31 | 32 | This provides a central location to react to files being imported, if later desired 33 | 34 | .PARAMETER Path 35 | The path to the file to load 36 | 37 | .EXAMPLE 38 | PS C:\> . Import-ModuleFile -File $function.FullName 39 | 40 | Imports the file stored in $function according to import policy 41 | #> 42 | [CmdletBinding()] 43 | Param ( 44 | [string] 45 | $Path 46 | ) 47 | 48 | $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath 49 | if ($doDotSource) { . $resolvedPath } 50 | else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) } 51 | } 52 | 53 | #region Load individual files 54 | if ($importIndividualFiles) 55 | { 56 | # Execute Preimport actions 57 | . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1" 58 | 59 | # Import all internal functions 60 | foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) 61 | { 62 | . Import-ModuleFile -Path $function.FullName 63 | } 64 | 65 | # Import all public functions 66 | foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) 67 | { 68 | . Import-ModuleFile -Path $function.FullName 69 | } 70 | 71 | # Execute Postimport actions 72 | . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1" 73 | 74 | # End it here, do not load compiled code below 75 | return 76 | } 77 | #endregion Load individual files 78 | 79 | #region Load compiled code 80 | "" 81 | #endregion Load compiled code -------------------------------------------------------------------------------- /DCManagement/bin/DCManagement.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveDirectoryManagementFramework/DCManagement/3b5359131ec94357dc2ffca8fc86ad5d9efe02b9/DCManagement/bin/DCManagement.dll -------------------------------------------------------------------------------- /DCManagement/bin/DCManagement.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ActiveDirectoryManagementFramework/DCManagement/3b5359131ec94357dc2ffca8fc86ad5d9efe02b9/DCManagement/bin/DCManagement.pdb -------------------------------------------------------------------------------- /DCManagement/bin/DCManagement.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DCManagement 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DCManagement/bin/readme.md: -------------------------------------------------------------------------------- 1 | # bin folder 2 | 3 | The bin folder exists to store binary data. And scripts related to the type system. 4 | 5 | This may include your own C#-based library, third party libraries you want to include (watch the license!), or a script declaring type accelerators (effectively aliases for .NET types) 6 | 7 | For more information on Type Accelerators, see the help on Set-PSFTypeAlias -------------------------------------------------------------------------------- /DCManagement/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.2.25 (2021-07-13) 4 | 5 | - Upd: Test-DCShare - added message tag DCTarget to allow message level modifiers to raise per-server processing messages. 6 | - Upd: Test-DCAccessRule - added message tag DCTarget to allow message level modifiers to raise per-server processing messages. 7 | - Upd: Test-DCAccessRule - added equivalent access detection. Will no longer report about inequal access rules, so long as they result in the same effective permissions. 8 | - Fix: Test-DCAccessRule - prompts for confirm on ToString when running in high confirm preference 9 | 10 | ## 1.2.21 (2021-04-23) 11 | 12 | - Upd: Shares - Added capability to specify which server to process through `-TargetServer` parameter. 13 | - Upd: AccessRules - Added capability to specify which server to process through `-TargetServer` parameter. 14 | - Fix: Test-DCAccessRule - errors when path not found: Cannot bind null to InputObject 15 | 16 | ## 1.2.18 (2020-10-11) 17 | 18 | - Upd: Removed most dependencies due to bug in PS5.1. Dependencies in ADMF itself are now expected to provide the necessary tools / modules. 19 | - Upd: Incremented PSFramework minimum version. 20 | 21 | ## 1.2.17 (2020-09-11) 22 | 23 | - Upd: Internal name resoloution update & dependency update 24 | 25 | ## 1.2.16 (2020-07-31) 26 | 27 | - New: Component - File System Access Rules 28 | - Upd: Install-DCChildDomain - shows context prompt when no configuration loaded yet 29 | - Upd: Install-DCDomainController - shows context prompt when no configuration loaded yet 30 | - Upd: Install-DCRootDomain - shows context prompt when no configuration loaded yet 31 | - Fix: Install-DCChildDomain - does not respect Sysvol configuration / parameter 32 | - Fix: Install-DCDomainController - does not respect Sysvol configuration / parameter 33 | - Fix: Install-DCRootDomain - does not respect Sysvol configuration / parameter 34 | - Fix: Install-DCDomainController - stops failing to install at all 35 | 36 | ## 1.1.8 (2020-07-03) 37 | 38 | - Fix: Install-DCDomainController will now install the DC role correctly before trying to join itself to a domain as DC 39 | 40 | ## 1.1.7 (2020-06-21) 41 | 42 | - New Component: Shares 43 | - New Command: Clear-DCConfiguration 44 | 45 | ## 1.0.5 (2020-01-27) 46 | 47 | - Metadata Update, no functional changes 48 | 49 | ## 1.0.4 (2019-12-21) 50 | 51 | - Initial Release 52 | -------------------------------------------------------------------------------- /DCManagement/en-us/about_DCManagement.help.txt: -------------------------------------------------------------------------------- 1 | TOPIC 2 | about_DCManagement 3 | 4 | SHORT DESCRIPTION 5 | Explains how to use the DCManagement powershell module 6 | 7 | LONG DESCRIPTION 8 | 9 | 10 | KEYWORDS 11 | DCManagement -------------------------------------------------------------------------------- /DCManagement/en-us/strings.psd1: -------------------------------------------------------------------------------- 1 | # This is where the strings go, that are written by 2 | # Write-PSFMessage, Stop-PSFFunction or the PSFramework validation scriptblocks 3 | @{ 4 | 'Assert-ADConnection.Failed' = 'Failed to connect to {0}' # $Server 5 | 6 | 'Assert-Configuration.NotConfigured' = 'No configuration data provided for: {0}' # $Type 7 | 8 | 'General.Invalid.Input' = 'Invalid input: {1}! This command only accepts output objects from {0}' # 'Test-DCAccessRule', $testItem 9 | 10 | 'Grant-ShareAccess.Execution.Failed' = 'Failed to grant access to {0} on \\{2}\{1} with status {3} : {4}' # $Identity, $Name, $ComputerName, $results.Status, $results.Message 11 | 'Grant-ShareAccess.WinRM.Failed' = 'Failed to grant access to {0} on \\{2}\{1}: Remoting access failed' # $Identity, $Name, $ComputerName 12 | 13 | 'Install-DCChildDomain.Installing' = 'Installing a new domain' # 14 | 'Install-DCChildDomain.Results' = 'Finished installation of domain {0}. The result object (including the SafeMode Administrator Password) has been stored in $DomainCreationResult' # $DomainName 15 | 16 | 'Install-DCDomainController.Installing' = 'Installing Domaincontroller into domain {0}' # $DomainName 17 | 'Install-DCDomainController.Results' = 'Finished installation of DC {0}. The result object (including the SafeMode Administrator Password) has been stored in $DCCreationResult' # $DnsName 18 | 19 | 'Install-DCRootDomain.Installing' = 'Installing a new forest' # 20 | 'Install-DCRootDomain.Results' = 'Finished installation of domain {0}. The result object (including the SafeMode Administrator Password) has been stored in $ForestCreationResult' # $DnsName 21 | 22 | 'Invoke-DCAccessRule.Access.Error' = 'Failed to connect to {0} using WinRM' # $testItem.Server 23 | 'Invoke-DCAccessRule.AccessRule.Add' = 'Granting {0} the right {1} ({2})' # $change.DisplayName, $change.FileSystemRights, $change.AccessControlType 24 | 'Invoke-DCAccessRule.AccessRule.Remove' = 'Revoking {0} the right {1} ({2})' # $change.DisplayName, $change.FileSystemRights, $change.AccessControlType 25 | 26 | 'Invoke-DCShare.Access.Error' = 'Failed to establish CIM session to {0}' # $testItem.Server 27 | 'Invoke-DCShare.Share.Create' = 'Creating share: {0}' # $testItem.Identity 28 | 'Invoke-DCShare.Share.Delete' = 'Deleting share: {0}' # $testItem.Identity 29 | 'Invoke-DCShare.Share.Migrate' = 'Migrating share to new folder: {0}' # $testItem.Identity 30 | 'Invoke-DCShare.Share.Update' = 'Updating share: {0}' # $testItem.Identity 31 | 'Invoke-DCShare.Share.UpdateAccess' = 'Updating share permissions on {0}: {1} > {2} ({3})' # $testItem.Identity, $accessEntry.Action, $accessEntry.Identity, $accessEntry.AccessRight 32 | 33 | 'Revoke-ShareAccess.Execution.Failed' = 'Failed to revoke access for {0} on \\{2}\{1} with status {3} : {4}' # $Identity, $Name, $ComputerName, $results.Status, $results.Message 34 | 'Revoke-ShareAccess.WinRM.Failed' = 'Failed to revoke access for {0} on \\{2}\{1}: Remoting access failed' # $Identity, $Name, $ComputerName 35 | 36 | 'Test-DCAccessRule.Identity.Error' = '{0} | Error resolving identities assigned permissions on {1}: Cannot resolve {2}' # $domainController.Name, $path.Name, ($errorCfg.Identity -join ",") 37 | 'Test-DCAccessRule.Path.ExistsNot' = '{0} | Configured path does not exist: {1}' # $domainController.Name, $path.Name 38 | 'Test-DCAccessRule.Processing' = '{0} | Processing' # $domainController.Name 39 | 'Test-DCAccessRule.Processing.Path' = '{0} | Testing access rules for {1}' # $domainController.Name, $path.Name 40 | 'Test-DCAccessRule.PSSession.Failed' = '{0} | Error connecting via WinRM' # $domainController.Name 41 | 42 | 'Test-DCShare.Access.IntegrityError' = 'Unable to resolve all identities for \\{1}\{0} . Skipping delegation check, as permissions integrity cannot be assured.' # $share.Name, $domainController.Name 43 | 'Test-DCShare.CimSession.Failed' = 'Failed to establish CIM session to {0}' # $domainController.Name 44 | 'Test-DCShare.Identity.Resolution.Failed' = 'Failed to resolve identity of {0}' # $entry 45 | 'Test-DCShare.Processing' = 'Processing shares on {0}' # $domainController.Name 46 | 47 | 'Validate.Child.DomainName' = 'Invalid domain name: {0}. Please enter a valid domain name without special characters and without the full dns tail (as it will be attached to the parent domain name)' # , 48 | 'Validate.ForestRoot.DnsDomainName' = 'Invalid DNS domain name: {0}. Please enter a valid DNS name that is not a single label domain name.' # , 49 | 'Validate.Parent.DnsDomainName' = 'Invalid DNS domain name: {0}. Please enter a valid DNS name that is not a single label domain name.' # , 50 | } -------------------------------------------------------------------------------- /DCManagement/functions/AccessRules/Get-DCAccessRule.ps1: -------------------------------------------------------------------------------- 1 | function Get-DCAccessRule 2 | { 3 | <# 4 | .SYNOPSIS 5 | Returns the list of registered filesystem access rules. 6 | 7 | .DESCRIPTION 8 | Returns the list of registered filesystem access rules. 9 | 10 | .PARAMETER Path 11 | Filter by the path it is assigned to. 12 | Defaults to: '*' 13 | 14 | .PARAMETER Identity 15 | Filter by the Identity granted permissions to. 16 | Default to: '*' 17 | 18 | .EXAMPLE 19 | PS C:\> Get-DCAccessRule 20 | 21 | Returns the list of all registered filesystem access rules. 22 | #> 23 | [CmdletBinding()] 24 | Param ( 25 | [string] 26 | $Path = '*', 27 | 28 | [string] 29 | $Identity = '*' 30 | ) 31 | 32 | process 33 | { 34 | ($script:fileSystemAccessRules.Values.Values | Where-Object Path -Like $Path | Where-Object Identity -Like $Identity) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /DCManagement/functions/AccessRules/Invoke-DCAccessRule.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DCAccessRule 2 | { 3 | <# 4 | .SYNOPSIS 5 | Applies the desired state for filesystem permissions on paths on relevant DCs. 6 | 7 | .DESCRIPTION 8 | Applies the desired state for filesystem permissions on paths on relevant DCs. 9 | Use Register-DCAccessRule to define the desired state. 10 | Use Test-DCAccessRule to test, what should be changed. 11 | By default, all pending access rule changes will be applied, specify the explicit test results you want to process to override this. 12 | 13 | .PARAMETER InputObject 14 | The specific test results produced by Test-DCAccessRule to apply. 15 | If you do not specify this parameter, ALL pending changes will be performed! 16 | 17 | .PARAMETER Server 18 | The server / domain to work with. 19 | 20 | .PARAMETER Credential 21 | The credentials to use for this operation. 22 | 23 | .PARAMETER TargetServer 24 | The specific server(s) to process. 25 | If specified, only listed domain controllers will be affected. 26 | Specify the full FQDN of the server. 27 | 28 | .PARAMETER EnableException 29 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 30 | This is less user friendly, but allows catching exceptions in calling scripts. 31 | 32 | .PARAMETER Confirm 33 | If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. 34 | 35 | .PARAMETER WhatIf 36 | If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. 37 | 38 | .EXAMPLE 39 | PS C:\> Invoke-DCAccessRule -Server corp.contoso.com 40 | 41 | Brings all DCs of the corp.contoso.com domain into their desired state as far as filesystem Access Rules are concerned. 42 | #> 43 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] 44 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 45 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")] 46 | [CmdletBinding(SupportsShouldProcess = $true)] 47 | param ( 48 | [Parameter(ValueFromPipeline = $true)] 49 | $InputObject, 50 | 51 | [PSFComputer] 52 | $Server, 53 | 54 | [PSCredential] 55 | $Credential, 56 | 57 | [string[]] 58 | $TargetServer, 59 | 60 | [switch] 61 | $EnableException 62 | ) 63 | 64 | begin 65 | { 66 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential 67 | if (-not $Server -and $TargetServer) { 68 | $parameters.Server = $TargetServer | Select-Object -First 1 69 | } 70 | $parameters['Debug'] = $false 71 | Assert-ADConnection @parameters -Cmdlet $PSCmdlet 72 | Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet 73 | Assert-Configuration -Type fileSystemAccessRules -Cmdlet $PSCmdlet 74 | Set-DCDomainContext @parameters 75 | 76 | $psCred = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential 77 | 78 | $psSessions = @{ } 79 | 80 | #region Functions 81 | function Add-AccessRule 82 | { 83 | [CmdletBinding()] 84 | param ( 85 | $Session, 86 | 87 | [string] 88 | $Path, 89 | 90 | $AccessRule 91 | ) 92 | 93 | $result = Invoke-Command -Session $Session -ScriptBlock { 94 | $referenceRule = $using:AccessRule 95 | try 96 | { 97 | $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( 98 | ([System.Security.Principal.SecurityIdentifier]$referenceRule.IdentityReference.ToString()), 99 | $referenceRule.FileSystemRights, 100 | $referenceRule.InheritanceFlags, 101 | $referenceRule.PropagationFlags, 102 | $referenceRule.AccessControlType 103 | ) 104 | $acl = Get-Acl -Path $using:Path -ErrorAction Stop 105 | $acl.AddAccessRule($rule) 106 | $acl | Set-Acl -Path $using:Path -ErrorAction Stop -Confirm:$false 107 | 108 | [PSCustomObject]@{ 109 | Success = $true 110 | Path = $using:Path 111 | Rule = $referenceRule 112 | Error = $null 113 | } 114 | } 115 | catch 116 | { 117 | [PSCustomObject]@{ 118 | Success = $false 119 | Path = $using:Path 120 | Rule = $referenceRule 121 | Error = $_ 122 | } 123 | } 124 | } 125 | if (-not $result.Success) 126 | { 127 | throw "Error: $($result.Error)" 128 | } 129 | } 130 | 131 | function Remove-AccessRule 132 | { 133 | [CmdletBinding()] 134 | param ( 135 | $Session, 136 | 137 | [string] 138 | $Path, 139 | 140 | $AccessRule 141 | ) 142 | 143 | $result = Invoke-Command -Session $Session -ScriptBlock { 144 | function Convert-UintToInt([uint32]$Number) { [System.BitConverter]::ToInt32(([System.BitConverter]::GetBytes($Number)), 0) } 145 | try 146 | { 147 | $referenceRule = $using:AccessRule 148 | $acl = Get-Acl -Path $using:Path -ErrorAction Stop 149 | foreach ($rule in $acl.Access) 150 | { 151 | if ($rule.IsInherited) { continue } 152 | if ($rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).ToString() -ne $referenceRule.IdentityReference.ToString()) { continue } 153 | if ([int]$rule.FileSystemRights -ne (Convert-UintToInt -Number $referenceRule.FileSystemRightsNumeric)) { continue } 154 | if ($rule.InheritanceFlags -ne $referenceRule.InheritanceFlags) { continue } 155 | if ($rule.PropagationFlags -ne $referenceRule.PropagationFlags) { continue } 156 | if ($rule.AccessControlType -ne $referenceRule.AccessControlType) { continue } 157 | $null = $acl.RemoveAccessRule($rule) 158 | } 159 | $acl | Set-Acl -Path $using:Path -ErrorAction Stop -Confirm:$false 160 | 161 | [PSCustomObject]@{ 162 | Success = $true 163 | Path = $using:Path 164 | Rule = $referenceRule 165 | Error = $null 166 | } 167 | } 168 | catch 169 | { 170 | [PSCustomObject]@{ 171 | Success = $false 172 | Path = $using:Path 173 | Rule = $using:AccessRule 174 | Error = $_ 175 | } 176 | } 177 | } 178 | if (-not $result.Success) 179 | { 180 | throw "Error: $($result.Error)" 181 | } 182 | } 183 | #endregion Functions 184 | } 185 | process 186 | { 187 | if ($TargetServer) { $parameters.TargetServer = $TargetServer } 188 | if (-not $InputObject) { $InputObject = Test-DCAccessRule @parameters } 189 | 190 | foreach ($testItem in ($InputObject | Sort-Object Type -Descending)) # Delete before Add 191 | { 192 | # Catch invalid input - can only process test results 193 | if ($testItem.PSObject.TypeNames -notcontains 'DCManagement.FSAccessRule.TestResult') 194 | { 195 | Stop-PSFFunction -String 'General.Invalid.Input' -StringValues 'Test-DCAccessRule', $testItem -Target $testItem -Continue -EnableException $EnableException 196 | } 197 | 198 | if (-not $psSessions[$testItem.Server]) 199 | { 200 | try { $psSessions[$testItem.Server] = New-PSSession -ComputerName $testItem.Server @psCred -ErrorAction Stop } 201 | catch { Stop-PSFFunction -String 'Invoke-DCAccessRule.Access.Error' -StringValues $testItem.Server -Target $testItem -Continue -EnableException $EnableException -ErrorRecord $_ } 202 | } 203 | $psSession = $psSessions[$testItem.Server] 204 | 205 | switch ($testItem.Type) 206 | { 207 | #region Add 208 | 'Add' 209 | { 210 | $change = @($testItem.Changed)[0] 211 | Invoke-PSFProtectedCommand -ActionString 'Invoke-DCAccessRule.AccessRule.Add' -ActionStringValues $change.DisplayName, $change.FileSystemRights, $change.AccessControlType -Target $testItem -ScriptBlock { 212 | Add-AccessRule -Session $psSession -Path $testItem.Identity -AccessRule $change 213 | } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue 214 | } 215 | #endregion Add 216 | #region Remove 217 | 'Remove' 218 | { 219 | $change = @($testItem.Changed)[0] 220 | Invoke-PSFProtectedCommand -ActionString 'Invoke-DCAccessRule.AccessRule.Remove' -ActionStringValues $change.DisplayName, $change.FileSystemRights, $change.AccessControlType -Target $testItem -ScriptBlock { 221 | Remove-AccessRule -Session $psSession -Path $testItem.Identity -AccessRule $change 222 | } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue 223 | } 224 | #endregion Remove 225 | } 226 | } 227 | } 228 | end 229 | { 230 | $psSessions.Values | Remove-PSSession -Confirm:$false -ErrorAction Ignore 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /DCManagement/functions/AccessRules/Register-DCAccessRule.ps1: -------------------------------------------------------------------------------- 1 | function Register-DCAccessRule 2 | { 3 | <# 4 | .SYNOPSIS 5 | Registers an access rule for FileSystem paths on a domain controller. 6 | 7 | .DESCRIPTION 8 | Registers an access rule for FileSystem paths on a domain controller. 9 | 10 | .PARAMETER Path 11 | The path to the filesystem object to grant permissions on. 12 | Supports string resolution. 13 | 14 | .PARAMETER Identity 15 | What identity / principal to grant access. 16 | Supports string resolution. 17 | 18 | .PARAMETER Rights 19 | What file system right to grant. 20 | 21 | .PARAMETER Type 22 | Whether this is an allow or a deny rule. 23 | Defaults to Allow. 24 | 25 | .PARAMETER Inheritance 26 | Who and how are access rules inherited. 27 | Defaults to 'ContainerInherit, ObjectInherit', meaning everything beneath the path inherits as well. 28 | 29 | .PARAMETER Propagation 30 | How access rules are being propagated. 31 | Defaults to "None", the windows default behavior. 32 | 33 | .PARAMETER Empty 34 | This path should have no explicit ACE defined. 35 | 36 | .PARAMETER AccessMode 37 | How filesystem access rules are processed. 38 | Supports three configurations: 39 | - Constrained: The default access mode, will remove any excess access rules. 40 | - Additive: Ignore any access rules already on the path, even if not configured 41 | - Defined: Ignore any access rules already on the path, even if not configured UNLESS the identity on those rules has an access level defined for it. 42 | 43 | .PARAMETER ServerRole 44 | What domain controller to apply this to: 45 | - All: All DCs in the enterprise 46 | - FSMO: Only DCs that have any FSMO role 47 | - PDC: Only the PDCEmulator 48 | 49 | .PARAMETER ContextName 50 | The name of the context defining the setting. 51 | This allows determining the configuration set that provided this setting. 52 | Used by the ADMF, available to any other configuration management solution. 53 | 54 | .EXAMPLE 55 | PS C:\> Get-Content .\accessrules.json | ConvertFrom-Json | Write-Output | Register-DCAccessRule 56 | 57 | Reads all access rule definitions from json and imports the definitions. 58 | #> 59 | [CmdletBinding()] 60 | param ( 61 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 62 | [string] 63 | $Path, 64 | 65 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ACE')] 66 | [string] 67 | $Identity, 68 | 69 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ACE')] 70 | [System.Security.AccessControl.FileSystemRights] 71 | $Rights, 72 | 73 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ACE')] 74 | [System.Security.AccessControl.AccessControlType] 75 | $Type = 'Allow', 76 | 77 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ACE')] 78 | [System.Security.AccessControl.InheritanceFlags] 79 | $Inheritance = 'ContainerInherit, ObjectInherit', 80 | 81 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ACE')] 82 | [System.Security.AccessControl.PropagationFlags] 83 | $Propagation = 'None', 84 | 85 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Empty')] 86 | [bool] 87 | $Empty, 88 | 89 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ACE')] 90 | [ValidateSet('Constrained', 'Additive', 'Defined')] 91 | [string] 92 | $AccessMode = 'Constrained', 93 | 94 | [Parameter(ValueFromPipelineByPropertyName = $true)] 95 | [ValidateSet('All', 'FSMO', 'PDC')] 96 | [string] 97 | $ServerRole = 'All', 98 | 99 | [string] 100 | $ContextName = '' 101 | ) 102 | 103 | process 104 | { 105 | if (-not $script:fileSystemAccessRules[$Path]) { $script:fileSystemAccessRules[$Path] = @{ } } 106 | 107 | $script:fileSystemAccessRules[$Path]["$($Identity)þ$($ServerRole)þ$($Rights)þ$($Type)þ$($Inheritance)þ$($Propagation)"] = [pscustomobject]@{ 108 | PSTypeName = 'DCManagement.AccessRule' 109 | Path = $Path 110 | Identity = $Identity 111 | Rights = $Rights 112 | Type = $Type 113 | Inheritance = $Inheritance 114 | Propagation = $Propagation 115 | AccessMode = $AccessMode 116 | ServerRole = $ServerRole 117 | Empty = $Empty 118 | ContextName = $ContextName 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /DCManagement/functions/AccessRules/Test-DCAccessRule.ps1: -------------------------------------------------------------------------------- 1 | function Test-DCAccessRule { 2 | <# 3 | .SYNOPSIS 4 | Tests all DCs, whether their NTFS filesystem Access Rules are configured as designed. 5 | 6 | .DESCRIPTION 7 | Tests all DCs, whether their NTFS filesystem Access Rules are configured as designed. 8 | This test ONLY considers paths, that are configured. 9 | In opposite to the DomainManagement AccessRule Component there is no system that considers part of the DCs filesystem as "under management". 10 | 11 | .PARAMETER Server 12 | The server / domain to work with. 13 | 14 | .PARAMETER Credential 15 | The credentials to use for this operation. 16 | 17 | .PARAMETER TargetServer 18 | The specific server(s) to process. 19 | If specified, only listed domain controllers will be affected. 20 | Specify the full FQDN of the server. 21 | 22 | .PARAMETER EnableException 23 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 24 | This is less user friendly, but allows catching exceptions in calling scripts. 25 | 26 | .EXAMPLE 27 | PS C:\> Test-DCAccessRule -Server corp.contoso.com 28 | 29 | Tests, whether the filesystem Access Rules on all DCs of the corp.contoso.com domain are configured as designed. 30 | #> 31 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")] 32 | [CmdletBinding()] 33 | param ( 34 | [PSFComputer] 35 | $Server, 36 | 37 | [PSCredential] 38 | $Credential, 39 | 40 | [string[]] 41 | $TargetServer, 42 | 43 | [switch] 44 | $EnableException 45 | ) 46 | 47 | begin { 48 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential 49 | if (-not $Server -and $TargetServer) { 50 | $parameters.Server = $TargetServer | Select-Object -First 1 51 | } 52 | $parameters['Debug'] = $false 53 | Assert-ADConnection @parameters -Cmdlet $PSCmdlet 54 | Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet 55 | Assert-Configuration -Type fileSystemAccessRules -Cmdlet $PSCmdlet 56 | Set-DCDomainContext @parameters 57 | 58 | $domainControllers = Get-DomainController @parameters 59 | $psCred = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential 60 | 61 | #region Utility Functions 62 | function ConvertFrom-AccessRuleDefinition { 63 | [CmdletBinding()] 64 | param ( 65 | [Parameter(ValueFromPipeline = $true)] 66 | $InputObject, 67 | 68 | [hashtable] 69 | $Parameters 70 | ) 71 | 72 | process { 73 | $resolvedPath = Resolve-String -Text $InputObject.Path -ArgumentList $Parameters 74 | 75 | if ($InputObject.Empty) { 76 | [PSCustomObject]@{ 77 | Path = $resolvedPath 78 | Identity = $null 79 | Principal = $null 80 | AccessRule = $null 81 | Configuration = $InputObject 82 | IdentityError = $false 83 | ServerRole = $InputObject.ServerRole 84 | AccessMode = 'Constrained' 85 | Empty = $true 86 | } 87 | return 88 | } 89 | 90 | $resolvedIdentity = Resolve-String -Text $InputObject.Identity -ArgumentList $Parameters 91 | $identityError = $false 92 | try { $resolvedPrincipal = Resolve-Principal @Parameters -Name $resolvedIdentity -OutputType SID -ErrorAction Stop } 93 | catch { 94 | $identityError = $true 95 | $resolvedPrincipal = [System.Security.Principal.NTAccount]$resolvedIdentity 96 | } 97 | 98 | $rule = [System.Security.AccessControl.FileSystemAccessRule]::new($resolvedPrincipal, $InputObject.Rights, $InputObject.Inheritance, $InputObject.Propagation, $InputObject.Type) 99 | Add-Member -InputObject $rule -MemberType NoteProperty -Name DisplayName -Value $resolvedIdentity 100 | 101 | [PSCustomObject]@{ 102 | Path = $resolvedPath 103 | Identity = $resolvedIdentity 104 | Principal = $resolvedPrincipal 105 | AccessRule = $rule 106 | Configuration = $InputObject 107 | IdentityError = $identityError 108 | ServerRole = $InputObject.ServerRole 109 | AccessMode = $InputObject.AccessMode 110 | Empty = $false 111 | } 112 | } 113 | } 114 | 115 | function Get-RemoteAccessRule { 116 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] 117 | [CmdletBinding()] 118 | param ( 119 | $Session, 120 | 121 | [string] 122 | $Path 123 | ) 124 | 125 | $rules = Invoke-Command -Session $Session -ScriptBlock { 126 | $acl = Get-Acl -Path $using:path 127 | foreach ($rule in $acl.Access) { 128 | if ($rule.IsInherited) { continue } 129 | [PSCustomObject]@{ 130 | PSTypeName = 'Remote.FileSystemAccessRule' 131 | DisplayName = $rule.IdentityReference.ToString() 132 | FileSystemRights = $rule.FileSystemRights 133 | FileSystemRightsNumeric = [int]$rule.FileSystemRights 134 | AccessControlType = $rule.AccessControlType 135 | IdentityReference = $rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) 136 | InheritanceFlags = $rule.InheritanceFlags 137 | PropagationFlags = $rule.PropagationFlags 138 | OriginalRights = $rule.FileSystemRights 139 | } 140 | } 141 | } 142 | # The default object had display issues when displayed in the "Change" property 143 | foreach ($rule in $rules) { 144 | $rule.FileSystemRights = Convert-AccessRight -Right $rule.FileSystemRightsNumeric 145 | $rule 146 | } 147 | } 148 | 149 | function New-Change { 150 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 151 | [CmdletBinding()] 152 | param ( 153 | $RuleObject 154 | ) 155 | 156 | Add-Member -InputObject $RuleObject -MemberType ScriptMethod -Name ToString -Force -Value { 157 | if ($this.DisplayName) { return $this.DisplayName } 158 | 159 | return $this.IdentityReference 160 | } -PassThru 161 | } 162 | 163 | function Test-AccessRule { 164 | [CmdletBinding()] 165 | param ( 166 | $RuleObject, 167 | 168 | $Reference, 169 | 170 | $AllRules 171 | ) 172 | 173 | foreach ($entry in $Reference) { 174 | if ($entry.FileSystemRights -ne $RuleObject.FileSystemRights) { continue } 175 | if ($entry.AccessControlType -ne $RuleObject.AccessControlType) { continue } 176 | if ($entry.IdentityReference.ToString() -ne $RuleObject.IdentityReference.ToString()) { continue } 177 | if ($entry.InheritanceFlags -ne $RuleObject.InheritanceFlags) { continue } 178 | if ($entry.PropagationFlags -ne $RuleObject.PropagationFlags) { continue } 179 | 180 | return $true 181 | } 182 | 183 | $rulesAffected = $AllRules | Where-Object { 184 | "$($_.IdentityReference)" -eq "$($RuleObject.IdentityReference)" -and 185 | $_.FileSystemRights -eq $RuleObject.FileSystemRights -and 186 | $_.AccessControlType -eq $RuleObject.AccessControlType 187 | } 188 | $referenceAffected = $Reference | Where-Object { 189 | "$($_.IdentityReference)" -eq "$($RuleObject.IdentityReference)" -and 190 | $_.FileSystemRights -eq $RuleObject.FileSystemRights -and 191 | $_.AccessControlType -eq $RuleObject.AccessControlType 192 | } 193 | 194 | if (-not $referenceAffected) { return $false } 195 | if ((@($rulesAffected).Count + @($referenceAffected).Count) -le 2) { return $false } 196 | 197 | $rulesFlags = Get-EffectiveFlags -AccessRules $rulesAffected 198 | $referenceFlags = Get-EffectiveFlags -AccessRules $referenceAffected 199 | if ( 200 | $rulesFlags.InheritanceFlags -eq $referenceFlags.InheritanceFlags -and 201 | $rulesFlags.PropagationFlags -eq $referenceFlags.PropagationFlags 202 | ) { 203 | return $true 204 | } 205 | 206 | return $false 207 | } 208 | 209 | function Get-EffectiveFlags { 210 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] 211 | [CmdletBinding()] 212 | param ( 213 | $AccessRules 214 | ) 215 | 216 | $affectsSelf = $false 217 | $affectsChildFiles = $false 218 | $affectsChildFolders = $false 219 | 220 | foreach ($rule in $AccessRules) { 221 | if ($rule.PropagationFlags -ne 'InheritOnly') { 222 | $affectsSelf = $true 223 | } 224 | if ($rule.PropagationFlags -ne 'NoPropagateInherit') { 225 | if ($rule.InheritanceFlags -band [System.Security.AccessControl.InheritanceFlags]::ContainerInherit) { 226 | $affectsChildFolders = $true 227 | } 228 | if ($rule.InheritanceFlags -band [System.Security.AccessControl.InheritanceFlags]::ObjectInherit) { 229 | $affectsChildFiles = $true 230 | } 231 | } 232 | } 233 | 234 | [PSCustomObject]@{ 235 | # ContainerInherit = 1; ObjectInherit = 2 236 | InheritanceFlags = [System.Security.AccessControl.InheritanceFlags](1 * $affectsChildFolders + 2 * $affectsChildFiles) 237 | # None = 0; InheritOnly = 2 | Flag only set when NOT affecting self 238 | PropagationFlags = [System.Security.AccessControl.PropagationFlags](2 * (-not $affectsSelf)) 239 | } 240 | } 241 | 242 | function Convert-AccessRight { 243 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseUsingScopeModifierInNewRunspaces", "")] 244 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")] 245 | [CmdletBinding()] 246 | param ( 247 | [int] 248 | $Right 249 | ) 250 | $bytes = [System.BitConverter]::GetBytes($Right) 251 | $uint = [System.BitConverter]::ToUInt32($bytes, 0) 252 | 253 | $definitiveRight = [DCManagement.FileSystemPermission]$uint 254 | 255 | # https://docs.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights 256 | # https://docs.microsoft.com/en-us/windows/win32/secauthz/standard-access-rights 257 | $genericRightsMap = @{ 258 | All = [DCManagement.FileSystemPermission]::FullControl 259 | Execute = ([DCManagement.FileSystemPermission]'ExecuteFile, ReadAttributes, ReadPermissions, Synchronize') 260 | Read = ([DCManagement.FileSystemPermission]'ReadAttributes, ReadData, ReadExtendedAttributes, ReadPermissions, Synchronize') 261 | Write = ([DCManagement.FileSystemPermission]'AppendData, WriteAttributes, WriteData, WriteExtendedAttributes, ReadPermissions, Synchronize') 262 | } 263 | 264 | if ($definitiveRight -band [DCManagement.FileSystemPermission]::GenericAll) { return [System.Security.AccessControl.FileSystemRights]::FullControl } 265 | if ($definitiveRight -band [DCManagement.FileSystemPermission]::GenericExecute) { 266 | $definitiveRight = $definitiveRight -bxor [DCManagement.FileSystemPermission]::GenericExecute -bor $genericRightsMap.Execute 267 | } 268 | if ($definitiveRight -band [DCManagement.FileSystemPermission]::GenericRead) { 269 | $definitiveRight = $definitiveRight -bxor [DCManagement.FileSystemPermission]::GenericRead -bor $genericRightsMap.Read 270 | } 271 | if ($definitiveRight -band [DCManagement.FileSystemPermission]::GenericWrite) { 272 | $definitiveRight = $definitiveRight -bxor [DCManagement.FileSystemPermission]::GenericWrite -bor $genericRightsMap.Write 273 | } 274 | [System.Security.AccessControl.FileSystemRights]$definitiveRight.Value__ 275 | } 276 | #endregion Utility Functions 277 | } 278 | process { 279 | foreach ($domainController in $domainControllers) { 280 | if ($TargetServer -and $domainController.Name -notin $TargetServer) { continue } 281 | $results = @{ 282 | ObjectType = 'FSAccessRule' 283 | Server = $domainController.Name 284 | } 285 | 286 | Write-PSFMessage -String 'Test-DCAccessRule.Processing' -StringValues $domainController.Name -Target $domainController.Name -Tag DCTarget 287 | try { $psSession = New-PSSession -ComputerName $domainController.Name @psCred -ErrorAction Stop } 288 | catch { Stop-PSFFunction -String 'Test-DCAccessRule.PSSession.Failed' -StringValues $domainController.Name -EnableException $EnableException -Cmdlet $PSCmdlet -Continue -Target $domainController.Name -ErrorRecord $_ } 289 | $accessConfigurations = Get-DCAccessRule | Where-Object { 290 | $_.ServerRole -eq 'ALL' -or 291 | ($_.ServerRole -eq 'FSMO' -and $domainController.IsFSMO) -or 292 | ($_.ServerRole -eq 'PDC' -and $domainController.IsPDCEmulator) 293 | } | ConvertFrom-AccessRuleDefinition -Parameters $parameters 294 | 295 | $groupedByPath = $accessConfigurations | Group-Object -Property Path 296 | foreach ($path in $groupedByPath) { 297 | Write-PSFMessage -String 'Test-DCAccessRule.Processing.Path' -StringValues $domainController.Name, $path.Name -Target $domainController.Name 298 | 299 | $pathExists = Invoke-Command -Session $psSession -ScriptBlock { Test-Path -Path $using:path.Name } 300 | if (-not $pathExists) { 301 | foreach ($entry in $path.Group) { 302 | New-TestResult @results -Type NoPath -Configuration $entry -Identity $path.Name -Changed (New-Change -RuleObject $entry.AccessRule) 303 | } 304 | Stop-PSFFunction -String 'Test-DCAccessRule.Path.ExistsNot' -StringValues $domainController.Name, $path.Name -EnableException $EnableException -Cmdlet $PSCmdlet -Continue -Target $domainController.Name 305 | } 306 | 307 | $existingRules = Get-RemoteAccessRule -Session $psSession -Path $path.Name 308 | 309 | #region Empty Mode: No explicit ACE should exist 310 | if ($path.Group | Where-Object Empty) { 311 | 312 | foreach ($rule in $existingRules) { 313 | New-TestResult @results -Type Remove -Configuration $path.Group -ADObject $existingRules -Identity $path.Name -Changed (New-Change -RuleObject $rule) 314 | } 315 | continue 316 | } 317 | #endregion Empty Mode: No explicit ACE should exist 318 | 319 | $effectiveMode = 'Additive' 320 | if ($path.Group | Where-Object AccessMode -EQ 'Defined') { $effectiveMode = 'Defined' } 321 | if ($path.Group | Where-Object AccessMode -EQ 'Constrained') { $effectiveMode = 'Constrained' } 322 | 323 | if ($path.Group | Where-Object IdentityError) { 324 | # Interrupt if Constrained and resolution error 325 | if ($effectiveMode -eq 'Constrained') { 326 | $errorCfg = $path.Group | Where-Object IdentityError 327 | Stop-PSFFunction -String 'Test-DCAccessRule.Identity.Error' -StringValues $domainController.Name, $path.Name, ($errorCfg.Identity -join ",") -EnableException $EnableException -Cmdlet $PSCmdlet -Continue -Target $domainController.Name 328 | } 329 | else { 330 | Write-PSFMessage -Level Warning -String 'Test-DCAccessRule.Identity.Error' -StringValues $domainController.Name, $path.Name, ($errorCfg.Identity -join ",") 331 | } 332 | } 333 | 334 | $effectiveDesiredState = $path.Group | Where-Object IdentityError -NE $true 335 | 336 | #region Compare desired state with existing state 337 | foreach ($desiredRule in $effectiveDesiredState) { 338 | if (Test-AccessRule -RuleObject $desiredRule.AccessRule -Reference $existingRules -AllRules $effectiveDesiredState.AccessRule) { continue } 339 | New-TestResult @results -Type Add -Configuration $desiredRule -ADObject $existingRules -Identity $path.Name -Changed (New-Change -RuleObject $desiredRule.AccessRule) 340 | } 341 | 342 | if ($effectiveMode -eq 'Additive') { continue } 343 | 344 | foreach ($existingRule in $existingRules) { 345 | if ($effectiveMode -eq 'Defined' -and "$($existingRule.IdentityReference.ToString())" -notin ($effectiveDesiredState.AccessRule.IdentityReference | ForEach-Object ToString -WhatIf:$false -Confirm:$false)) { continue } 346 | if (Test-AccessRule -RuleObject $existingRule -Reference $effectiveDesiredState.AccessRule -AllRules $existingRules) { continue } 347 | New-TestResult @results -Type Remove -Configuration $effectiveDesiredState -ADObject $existingRule -Identity $path.Name -Changed (New-Change -RuleObject $existingRule) 348 | } 349 | #endregion Compare desired state with existing state 350 | } 351 | 352 | Remove-PSSession -Session $psSession -ErrorAction Ignore -Confirm:$false 353 | } 354 | } 355 | } -------------------------------------------------------------------------------- /DCManagement/functions/AccessRules/Unregister-DCAccessRule.ps1: -------------------------------------------------------------------------------- 1 | function Unregister-DCAccessRule 2 | { 3 | <# 4 | .SYNOPSIS 5 | Removes an access rule from the list of registered access rules. 6 | 7 | .DESCRIPTION 8 | Removes an access rule from the list of registered access rules. 9 | 10 | .PARAMETER Path 11 | The path to the filesystem resource being managed. 12 | 13 | .PARAMETER Identity 14 | The identity (user, group, etc.) whose permissions ar being removed from the list of intended permissions. 15 | 16 | .PARAMETER ServerRole 17 | The processing mode the rule was assigned to. 18 | 19 | .PARAMETER Rights 20 | The rights assigned. 21 | 22 | .PARAMETER Type 23 | Allow or Deny rule? 24 | 25 | .PARAMETER Inheritance 26 | Who gets to inherit? 27 | 28 | .PARAMETER Propagation 29 | How does it propagate? 30 | 31 | .EXAMPLE 32 | PS C:\> Get-DCaccessRule | Unregister-DCAccessRule 33 | 34 | Clears all configured access rules. 35 | #> 36 | [CmdletBinding()] 37 | param ( 38 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 39 | [string] 40 | $Path, 41 | 42 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 43 | [string] 44 | $Identity, 45 | 46 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 47 | [string] 48 | $ServerRole, 49 | 50 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 51 | [string] 52 | $Rights, 53 | 54 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 55 | [string] 56 | $Type, 57 | 58 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 59 | [string] 60 | $Inheritance, 61 | 62 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 63 | [string] 64 | $Propagation 65 | ) 66 | 67 | process 68 | { 69 | if (-not $script:fileSystemAccessRules[$Path]) { return } 70 | 71 | $script:fileSystemAccessRules[$Path].Remove("$($Identity)þ$($ServerRole)þ$($Rights)þ$($Type)þ$($Inheritance)þ$($Propagation)") 72 | 73 | if (-not $script:fileSystemAccessRules[$Path]) { $script:fileSystemAccessRules.Remove($Path) } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /DCManagement/functions/deployment/Install-DCChildDomain.ps1: -------------------------------------------------------------------------------- 1 | function Install-DCChildDomain 2 | { 3 | <# 4 | .SYNOPSIS 5 | Installs a child domain. 6 | 7 | .DESCRIPTION 8 | Installs a child domain. 9 | 10 | .PARAMETER ComputerName 11 | The server to promote to a DC hosting a new subdomain. 12 | 13 | .PARAMETER Credential 14 | The credentials to use for connecting to the DC-to-be. 15 | 16 | .PARAMETER DomainName 17 | The name of the domain to install. 18 | Note: Only specify the first DNS element, not the full fqdn of the domain. 19 | (The component usually representing the Netbios Name) 20 | 21 | .PARAMETER ParentDomainName 22 | The FQDN of the parent domain. 23 | 24 | .PARAMETER NetBiosName 25 | The NetBios name of the domain. 26 | Will use the DomainName if not specified. 27 | 28 | .PARAMETER SafeModeAdministratorPassword 29 | The SafeModeAdministratorPassword specified during domain creation. 30 | If not specified, a random password will be chosen. 31 | The password is part of the return values. 32 | 33 | .PARAMETER EnterpriseAdminCredential 34 | The Credentials of an Enterprise administrator. 35 | Will prompt for credentials if not specified. 36 | 37 | .PARAMETER NoDNS 38 | Disables installation and configuration of the DNS role as part of the installation. 39 | 40 | .PARAMETER NoReboot 41 | Prevents reboot of the server after installation. 42 | Note: Generally a reboot is required before proceeding, disabling this will lead to having to manually reboot the computer. 43 | 44 | .PARAMETER LogPath 45 | The path where the NTDS logs should be stored. 46 | 47 | .PARAMETER SysvolPath 48 | The path where SYSVOL should be stored. 49 | 50 | .PARAMETER DatabasePath 51 | The path where the NTDS database is being stored. 52 | 53 | .PARAMETER NoResultCache 54 | Disables caching of the command's return object. 55 | By default, this command will cache the return object as a global variable. 56 | 57 | .PARAMETER EnableException 58 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 59 | This is less user friendly, but allows catching exceptions in calling scripts. 60 | 61 | .PARAMETER Confirm 62 | If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. 63 | 64 | .PARAMETER WhatIf 65 | If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. 66 | 67 | .EXAMPLE 68 | PS C:\> Install-DCChildDomain -ComputerName 10.1.2.3 -Credential $cred -DomainName corp -ParentDomainName contoso.com 69 | 70 | Will install the childdomain corp.contoso.com under the domain contoso.com on the server 10.1.2.3. 71 | #> 72 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] 73 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] 74 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 75 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] 76 | [CmdletBinding(SupportsShouldProcess = $true)] 77 | Param ( 78 | [PSFComputer] 79 | $ComputerName = 'localhost', 80 | 81 | [PSCredential] 82 | $Credential, 83 | 84 | [Parameter(Mandatory = $true)] 85 | [PsfValidatePattern('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9])$', ErrorString = 'DCManagement.Validate.Child.DomainName')] 86 | [string] 87 | $DomainName, 88 | 89 | [Parameter(Mandatory = $true)] 90 | [PsfValidatePattern('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9])){1,}$', ErrorString = 'DCManagement.Validate.Parent.DnsDomainName')] 91 | [string] 92 | $ParentDomainName, 93 | 94 | [string] 95 | $NetBiosName, 96 | 97 | [securestring] 98 | $SafeModeAdministratorPassword = (New-Password -Length 32 -AsSecureString), 99 | 100 | [PSCredential] 101 | $EnterpriseAdminCredential = (Get-Credential -Message "Enter credentials for Enterprise Administrator to create child domain"), 102 | 103 | [switch] 104 | $NoDNS, 105 | 106 | [switch] 107 | $NoReboot, 108 | 109 | [string] 110 | $LogPath, 111 | 112 | [string] 113 | $SysvolPath, 114 | 115 | [string] 116 | $DatabasePath, 117 | 118 | [switch] 119 | $NoResultCache, 120 | 121 | [switch] 122 | $EnableException 123 | ) 124 | 125 | begin 126 | { 127 | $parameters = @{ Server = $ComputerName; IsDCInstall = $true } 128 | if ($Credential) { $parameters['Credential'] = $Credential } 129 | Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet 130 | 131 | $NoDNS = Resolve-ParameterValue -FullName 'DCManagement.Defaults.NoDNS' -InputObject $NoDNS 132 | $NoReboot = Resolve-ParameterValue -FullName 'DCManagement.Defaults.NoReboot' -InputObject $NoReboot 133 | $LogPath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.LogPath' -InputObject $LogPath 134 | $SysvolPath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.SysvolPath' -InputObject $SysvolPath 135 | $DatabasePath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.DatabasePath' -InputObject $DatabasePath 136 | 137 | #region Scriptblock 138 | $scriptBlock = { 139 | param ($Configuration) 140 | 141 | function New-Result { 142 | [CmdletBinding()] 143 | param ( 144 | [ValidateSet('Success', 'Error')] 145 | [string] 146 | $Status = 'Success', 147 | 148 | [string] 149 | $Message, 150 | 151 | $ErrorRecord, 152 | 153 | $Data 154 | ) 155 | 156 | [PSCustomObject]@{ 157 | Status = $Status 158 | Success = $Status -eq 'Success' 159 | Message = $Message 160 | Error = $ErrorRecord 161 | Data = $Data 162 | SafeModeAdminPassword = $null 163 | } 164 | } 165 | 166 | # Check whether domain member 167 | $computerSystem = Get-CimInstance win32_ComputerSystem 168 | if ($computerSystem.PartOfDomain) { 169 | New-Result -Status Error -Message "Computer $env:COMPUTERNAME is part of AD domain: $($computerSystem.Domain)" 170 | return 171 | } 172 | 173 | $parameters = @{ 174 | NewDomainName = $Configuration.NewDomainName 175 | NewDomainNetBiosName = $Configuration.NewDomainNetBiosName 176 | ParentDomainName = $Configuration.ParentDomainName 177 | Credential = $Configuration.EnterpriseAdminCredential 178 | DomainMode = 'Win2012R2' 179 | DatabasePath = $Configuration.DatabasePath 180 | LogPath = $Configuration.LogPath 181 | SysvolPath = $Configuration.SysvolPath 182 | InstallDNS = $Configuration.InstallDNS 183 | SafeModeAdministratorPassword = $Configuration.SafeModeAdministratorPassword 184 | NoRebootOnCompletion = $Configuration.NoRebootOnCompletion 185 | } 186 | 187 | # Test Installation 188 | $testResult = Test-ADDSDomainInstallation @parameters -WarningAction SilentlyContinue 189 | if ($testResult.Status -eq "Error") { 190 | New-Result -Status Error -Message "Failed validating Domain Installation: $($testResult.Message)" -Data $testResult 191 | return 192 | } 193 | 194 | # Execute Installation 195 | try { 196 | $resultData = Install-ADDSDomain @parameters -ErrorAction Stop -Confirm:$false -WarningAction SilentlyContinue 197 | if ($resultData.Status -eq "Error") { 198 | New-Result -Status Error -Message "Failed installing domain: $($resultData.Message)" -Data $resultData 199 | return 200 | } 201 | New-Result -Status 'Success' -Message "Domain $($Configuration.NewDomainName) successfully installed" -Data $resultData 202 | return 203 | } 204 | catch { 205 | New-Result -Status Error -Message "Error executing domain deployment: $_" -ErrorRecord $_ 206 | return 207 | } 208 | } 209 | #endregion Scriptblock 210 | } 211 | process 212 | { 213 | if (-not $NetBiosName) { $NetBiosName = $DomainName } 214 | $configuration = [PSCustomObject]@{ 215 | NewDomainName = $DomainName 216 | NewDomainNetBiosName = $NetBiosName 217 | ParentDomainName = $ParentDomainName 218 | EnterpriseAdminCredential = $EnterpriseAdminCredential 219 | InstallDNS = (-not $NoDNS) 220 | LogPath = $LogPath 221 | SysvolPath = $SysvolPath 222 | DatabasePath = $DatabasePath 223 | NoRebootOnCompletion = $NoReboot 224 | SafeModeAdministratorPassword = $SafeModeAdministratorPassword 225 | } 226 | 227 | Invoke-PSFProtectedCommand -ActionString 'Install-DCChildDomain.Installing' -Target $DomainName -ScriptBlock { 228 | $result = Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock -ErrorAction Stop -ArgumentList $configuration 229 | $result.SafeModeAdminPassword = $SafeModeAdministratorPassword 230 | $result = $result | Select-PSFObject -KeepInputObject -ScriptProperty @{ 231 | Password = { 232 | [PSCredential]::new("Foo", $this.SafeModeAdminPassword).GetNetworkCredential().Password 233 | } 234 | } -ShowProperty Success, Message 235 | if (-not $NoResultCache) { 236 | $global:DomainCreationResult = $result 237 | } 238 | $result 239 | } -EnableException $EnableException -PSCmdlet $PSCmdlet 240 | if (Test-PSFFunctionInterrupt) { return } 241 | 242 | if (-not $NoResultCache) { 243 | Write-PSFMessage -Level Host -String 'Install-DCChildDomain.Results' -StringValues $DomainName 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /DCManagement/functions/deployment/Install-DCDomainController.ps1: -------------------------------------------------------------------------------- 1 | function Install-DCDomainController 2 | { 3 | <# 4 | .SYNOPSIS 5 | Adds a new domain controller to an existing domain. 6 | 7 | .DESCRIPTION 8 | Adds a new domain controller to an existing domain. 9 | The target computer cannot already be part of the domain. 10 | 11 | .PARAMETER ComputerName 12 | The target to promote to domain controller. 13 | Accepts and reuses an already established PowerShell Remoting Session. 14 | 15 | .PARAMETER Credential 16 | Credentials to use for authenticating to the computer account being promoted. 17 | 18 | .PARAMETER DomainName 19 | The fully qualified dns name of the domain to join the DC to. 20 | 21 | .PARAMETER DomainCredential 22 | Credentials to use when authenticating to the domain. 23 | 24 | .PARAMETER SafeModeAdministratorPassword 25 | The password to use as SafeModeAdministratorPassword. 26 | Autogenerates and reports a new password if not specified. 27 | 28 | .PARAMETER NoDNS 29 | Disable deploying a DNS service with the new domain controller. 30 | 31 | .PARAMETER NoReboot 32 | Prevent reboot after finishing deployment 33 | 34 | .PARAMETER LogPath 35 | The path where the DC will store the logs. 36 | 37 | .PARAMETER SysvolPath 38 | The path where the DC will store sysvol. 39 | 40 | .PARAMETER DatabasePath 41 | The path where the DC will store NTDS Database. 42 | 43 | .PARAMETER NoResultCache 44 | Disables caching the result object of the operation. 45 | By default, this command will cache the result of the installation (including the SafeModeAdministratorPassword), to reduce the risk of user error. 46 | 47 | .PARAMETER EnableException 48 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 49 | This is less user friendly, but allows catching exceptions in calling scripts. 50 | 51 | .PARAMETER Confirm 52 | If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. 53 | 54 | .PARAMETER WhatIf 55 | If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. 56 | 57 | .EXAMPLE 58 | PS C:\> Install-DCDomainController -Computer dc2.contoso.com -Credential $localCred -DomainName 'contoso.com' -DomainCredential $domCred 59 | 60 | Joins the server dc2.contoso.com into the contoso.com domain, as a promoted domain controller using the specified credentials. 61 | #> 62 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] 63 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] 64 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 65 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] 66 | [CmdletBinding(SupportsShouldProcess = $true)] 67 | Param ( 68 | [PSFComputer] 69 | $ComputerName = 'localhost', 70 | 71 | [PSCredential] 72 | $Credential, 73 | 74 | [Parameter(Mandatory = $true)] 75 | [PsfValidatePattern('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9])){1,}$', ErrorString = 'DCManagement.Validate.ForestRoot.DnsDomainName')] 76 | [string] 77 | $DomainName, 78 | 79 | [PSCredential] 80 | $DomainCredential = (Get-Credential -Message 'Specify domain admin credentials needed to authorize the promotion to domain controller'), 81 | 82 | [securestring] 83 | $SafeModeAdministratorPassword = (New-Password -Length 32 -AsSecureString), 84 | 85 | [switch] 86 | $NoDNS, 87 | 88 | [switch] 89 | $NoReboot, 90 | 91 | [string] 92 | $LogPath, 93 | 94 | [string] 95 | $SysvolPath, 96 | 97 | [string] 98 | $DatabasePath, 99 | 100 | [switch] 101 | $NoResultCache, 102 | 103 | [switch] 104 | $EnableException 105 | ) 106 | 107 | begin 108 | { 109 | $parameters = @{ Server = $ComputerName; IsDCInstall = $true } 110 | if ($Credential) { $parameters['Credential'] = $Credential } 111 | Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet 112 | 113 | $NoDNS = Resolve-ParameterValue -FullName 'DCManagement.Defaults.NoDNS' -InputObject $NoDNS 114 | $NoReboot = Resolve-ParameterValue -FullName 'DCManagement.Defaults.NoReboot' -InputObject $NoReboot 115 | $LogPath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.LogPath' -InputObject $LogPath 116 | $SysvolPath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.SysvolPath' -InputObject $SysvolPath 117 | $DatabasePath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.DatabasePath' -InputObject $DatabasePath 118 | 119 | #region Main Scriptblock 120 | $scriptBlock = { 121 | param ( 122 | $Configuration 123 | ) 124 | function New-Result { 125 | [CmdletBinding()] 126 | param ( 127 | [ValidateSet('Success', 'Error')] 128 | [string] 129 | $Status = 'Success', 130 | 131 | [string] 132 | $Message, 133 | 134 | $ErrorRecord, 135 | 136 | $Data 137 | ) 138 | 139 | [PSCustomObject]@{ 140 | Status = $Status 141 | Success = $Status -eq 'Success' 142 | Message = $Message 143 | Error = $ErrorRecord 144 | Data = $Data 145 | SafeModeAdminPassword = $null 146 | } 147 | } 148 | 149 | # Check whether domain member 150 | $computerSystem = Get-CimInstance win32_ComputerSystem 151 | if ($computerSystem.PartOfDomain) { 152 | New-Result -Status Error -Message "Computer $env:COMPUTERNAME is part of AD domain: $($computerSystem.Domain)" 153 | return 154 | } 155 | 156 | $null = Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools 157 | 158 | $parameters = @{ 159 | DomainName = $Configuration.DomainName 160 | Credential = $Configuration.DomainCredential 161 | DatabasePath = $Configuration.DatabasePath 162 | LogPath = $Configuration.LogPath 163 | SysvolPath = $Configuration.SysvolPath 164 | InstallDNS = $Configuration.InstallDNS 165 | SafeModeAdministratorPassword = $Configuration.SafeModeAdministratorPassword 166 | NoRebootOnCompletion = $Configuration.NoRebootOnCompletion 167 | } 168 | 169 | # Test Installation 170 | $testResult = Test-ADDSDomainControllerInstallation @parameters -WarningAction SilentlyContinue 171 | if ($testResult.Status -eq "Error") { 172 | New-Result -Status Error -Message "Failed validating Domain Controller Installation: $($testResult.Message)" -Data $testResult 173 | return 174 | } 175 | 176 | # Execute Installation 177 | try { 178 | $resultData = Install-ADDSDomainController @parameters -ErrorAction Stop -Confirm:$false -WarningAction SilentlyContinue 179 | if ($resultData.Status -eq "Error") { 180 | New-Result -Status Error -Message "Failed installing Domain Controller: $($resultData.Message)" -Data $resultData 181 | return 182 | } 183 | New-Result -Status 'Success' -Message "Domain $($Configuration.DomainName) successfully installed" -Data $resultData 184 | return 185 | } 186 | catch { 187 | New-Result -Status Error -Message "Error executing Domain Controller deployment: $_" -ErrorRecord $_ 188 | return 189 | } 190 | } 191 | #endregion Main Scriptblock 192 | } 193 | process 194 | { 195 | if (-not $NetBiosName) { $NetBiosName = $DnsName -split "\." | Select-Object -First 1 } 196 | $configuration = [PSCustomObject]@{ 197 | DomainName = $DomainName 198 | DomainCredential = $DomainCredential 199 | SafeModeAdministratorPassword = $SafeModeAdministratorPassword 200 | InstallDNS = (-not $NoDNS) 201 | LogPath = $LogPath 202 | SysvolPath = $SysvolPath 203 | DatabasePath = $DatabasePath 204 | NoRebootOnCompletion = $NoReboot 205 | } 206 | 207 | Invoke-PSFProtectedCommand -ActionString 'Install-DCDomainController.Installing' -ActionStringValues $DomainName -Target $DnsName -ScriptBlock { 208 | $result = Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock -ErrorAction Stop -ArgumentList $configuration 209 | $result.SafeModeAdminPassword = $SafeModeAdministratorPassword 210 | $result = $result | Select-PSFObject -KeepInputObject -ScriptProperty @{ 211 | Password = { 212 | [PSCredential]::new("Foo", $this.SafeModeAdminPassword).GetNetworkCredential().Password 213 | } 214 | } -ShowProperty Success, Message 215 | if (-not $NoResultCache) { 216 | $global:DCCreationResult = $result 217 | } 218 | $result 219 | } -EnableException $EnableException -PSCmdlet $PSCmdlet 220 | if (Test-PSFFunctionInterrupt) { return } 221 | 222 | if (-not $NoResultCache) { 223 | Write-PSFMessage -Level Host -String 'Install-DCDomainController.Results' -StringValues $DnsName 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /DCManagement/functions/deployment/Install-DCRootDomain.ps1: -------------------------------------------------------------------------------- 1 | function Install-DCRootDomain 2 | { 3 | <# 4 | .SYNOPSIS 5 | Deploys a new forest / root domain. 6 | 7 | .DESCRIPTION 8 | Deploys a new forest / root domain. 9 | 10 | .PARAMETER ComputerName 11 | The computer on which to install it. 12 | Uses WinRM / PowerShell remoting if not local execution. 13 | 14 | .PARAMETER Credential 15 | The credentials to use for this operation. 16 | 17 | .PARAMETER DnsName 18 | The name of the new domain & forest. 19 | 20 | .PARAMETER NetBiosName 21 | The netbios name of the new domain. 22 | If not specified, it will automatically use the first element of the DNS name 23 | 24 | .PARAMETER SafeModeAdministratorPassword 25 | The password to use as SafeModeAdministratorPassword. 26 | Autogenerates and reports a new password if not specified. 27 | 28 | .PARAMETER NoDNS 29 | Disable deploying a DNS service with the new forest. 30 | 31 | .PARAMETER NoReboot 32 | Prevent reboot after finishing deployment 33 | 34 | .PARAMETER LogPath 35 | The path where the DC will store the logs. 36 | 37 | .PARAMETER SysvolPath 38 | The path where the DC will store sysvol. 39 | 40 | .PARAMETER DatabasePath 41 | The path where the DC will store NTDS Database. 42 | 43 | .PARAMETER NoResultCache 44 | Disables caching the result object of the operation. 45 | By default, this command will cache the result of the installation (including the SafeModeAdministratorPassword), to reduce the risk of user error. 46 | 47 | .PARAMETER EnableException 48 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 49 | This is less user friendly, but allows catching exceptions in calling scripts. 50 | 51 | .PARAMETER Confirm 52 | If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. 53 | 54 | .PARAMETER WhatIf 55 | If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. 56 | 57 | .EXAMPLE 58 | PS C:\> Install-DCRootDomain -DnsName 'contoso.com' 59 | 60 | Creates the forest "contoso.com" while promoting the computer as DC. 61 | #> 62 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] 63 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")] 64 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 65 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")] 66 | [CmdletBinding(SupportsShouldProcess = $true)] 67 | Param ( 68 | [PSFComputer] 69 | $ComputerName = 'localhost', 70 | 71 | [PSCredential] 72 | $Credential, 73 | 74 | [Parameter(Mandatory = $true)] 75 | [PsfValidatePattern('^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-_]{0,61}[a-zA-Z0-9])){1,}$', ErrorString = 'DCManagement.Validate.ForestRoot.DnsDomainName')] 76 | [string] 77 | $DnsName, 78 | 79 | [string] 80 | $NetBiosName, 81 | 82 | [securestring] 83 | $SafeModeAdministratorPassword = (New-Password -Length 32 -AsSecureString), 84 | 85 | [switch] 86 | $NoDNS, 87 | 88 | [switch] 89 | $NoReboot, 90 | 91 | [string] 92 | $LogPath, 93 | 94 | [string] 95 | $SysvolPath, 96 | 97 | [string] 98 | $DatabasePath, 99 | 100 | [switch] 101 | $NoResultCache, 102 | 103 | [switch] 104 | $EnableException 105 | ) 106 | 107 | begin 108 | { 109 | $parameters = @{ Server = $ComputerName; IsDCInstall = $true } 110 | if ($Credential) { $parameters['Credential'] = $Credential} 111 | Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet 112 | 113 | $NoDNS = Resolve-ParameterValue -FullName 'DCManagement.Defaults.NoDNS' -InputObject $NoDNS 114 | $NoReboot = Resolve-ParameterValue -FullName 'DCManagement.Defaults.NoReboot' -InputObject $NoReboot 115 | $LogPath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.LogPath' -InputObject $LogPath 116 | $SysvolPath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.SysvolPath' -InputObject $SysvolPath 117 | $DatabasePath = Resolve-ParameterValue -FullName 'DCManagement.Defaults.DatabasePath' -InputObject $DatabasePath 118 | 119 | 120 | #region Main Scriptblock 121 | $scriptBlock = { 122 | param ( 123 | $Configuration 124 | ) 125 | function New-Result { 126 | [CmdletBinding()] 127 | param ( 128 | [ValidateSet('Success', 'Error')] 129 | [string] 130 | $Status = 'Success', 131 | 132 | [string] 133 | $Message, 134 | 135 | $ErrorRecord, 136 | 137 | $Data 138 | ) 139 | 140 | [PSCustomObject]@{ 141 | Status = $Status 142 | Success = $Status -eq 'Success' 143 | Message = $Message 144 | Error = $ErrorRecord 145 | Data = $Data 146 | SafeModeAdminPassword = $null 147 | } 148 | } 149 | 150 | # Check whether domain member 151 | $computerSystem = Get-CimInstance win32_ComputerSystem 152 | if ($computerSystem.PartOfDomain) { 153 | New-Result -Status Error -Message "Computer $env:COMPUTERNAME is part of AD domain: $($computerSystem.Domain)" 154 | return 155 | } 156 | 157 | $parameters = @{ 158 | DomainName = $Configuration.DnsName 159 | DomainMode = 'Win2012R2' 160 | DomainNetbiosName = $Configuration.NetBiosName 161 | ForestMode = 'Win2012R2' 162 | DatabasePath = $Configuration.DatabasePath 163 | LogPath = $Configuration.LogPath 164 | SysvolPath = $Configuration.SysvolPath 165 | InstallDNS = $Configuration.InstallDNS 166 | SafeModeAdministratorPassword = $Configuration.SafeModeAdministratorPassword 167 | NoRebootOnCompletion = $Configuration.NoRebootOnCompletion 168 | } 169 | 170 | # Test Installation 171 | $testResult = Test-ADDSForestInstallation @parameters -WarningAction SilentlyContinue 172 | if ($testResult.Status -eq "Error") { 173 | New-Result -Status Error -Message "Failed validating Forest Installation: $($testResult.Message)" -Data $testResult 174 | return 175 | } 176 | 177 | # Execute Installation 178 | try { 179 | $resultData = Install-ADDSForest @parameters -ErrorAction Stop -Confirm:$false -WarningAction SilentlyContinue 180 | if ($resultData.Status -eq "Error") { 181 | New-Result -Status Error -Message "Failed installing Forest: $($resultData.Message)" -Data $resultData 182 | return 183 | } 184 | New-Result -Status 'Success' -Message "Domain $($Configuration.DnsName) successfully installed" -Data $resultData 185 | return 186 | } 187 | catch { 188 | New-Result -Status Error -Message "Error executing forest deployment: $_" -ErrorRecord $_ 189 | return 190 | } 191 | } 192 | #endregion Main Scriptblock 193 | } 194 | process 195 | { 196 | if (-not $NetBiosName) { $NetBiosName = $DnsName -split "\." | Select-Object -First 1 } 197 | $configuration = [PSCustomObject]@{ 198 | DnsName = $DnsName 199 | NetBiosName = $NetBiosName 200 | SafeModeAdministratorPassword = $SafeModeAdministratorPassword 201 | InstallDNS = (-not $NoDNS) 202 | LogPath = $LogPath 203 | SysvolPath = $SysvolPath 204 | DatabasePath = $DatabasePath 205 | NoRebootOnCompletion = $NoReboot 206 | } 207 | 208 | Invoke-PSFProtectedCommand -ActionString 'Install-DCRootDomain.Installing' -Target $DnsName -ScriptBlock { 209 | $result = Invoke-PSFCommand -ComputerName $ComputerName -Credential $Credential -ScriptBlock $scriptBlock -ErrorAction Stop -ArgumentList $configuration 210 | $result.SafeModeAdminPassword = $SafeModeAdministratorPassword 211 | $result = $result | Select-PSFObject -KeepInputObject -ScriptProperty @{ 212 | Password = { 213 | [PSCredential]::new("Foo", $this.SafeModeAdminPassword).GetNetworkCredential().Password 214 | } 215 | } -ShowProperty Success, Message 216 | if (-not $NoResultCache) { 217 | $global:ForestCreationResult = $result 218 | } 219 | $result 220 | } -EnableException $EnableException -PSCmdlet $PSCmdlet 221 | if (Test-PSFFunctionInterrupt) { return } 222 | 223 | if (-not $NoResultCache) { 224 | Write-PSFMessage -Level Host -String 'Install-DCRootDomain.Results' -StringValues $DnsName 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /DCManagement/functions/readme.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | This is the folder where the functions go. 4 | 5 | Depending on the complexity of the module, it is recommended to subdivide them into subfolders. 6 | 7 | The module will pick up all .ps1 files recursively -------------------------------------------------------------------------------- /DCManagement/functions/shares/Get-DCShare.ps1: -------------------------------------------------------------------------------- 1 | function Get-DCShare 2 | { 3 | <# 4 | .SYNOPSIS 5 | Returns the list of registered shares. 6 | 7 | .DESCRIPTION 8 | Returns the list of registered shares. 9 | 10 | .PARAMETER Name 11 | Filter the returned share definitions by name. 12 | Defaults to '*' 13 | 14 | .EXAMPLE 15 | PS C:\> Get-DCShare 16 | 17 | Returns the list of registered shares. 18 | #> 19 | [CmdletBinding()] 20 | Param ( 21 | [string] 22 | $Name = '*' 23 | ) 24 | 25 | process 26 | { 27 | $script:shares.Values | Where-Object Name -like $Name 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DCManagement/functions/shares/Invoke-DCShare.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DCShare 2 | { 3 | <# 4 | .SYNOPSIS 5 | Brings all network shares on all DCs in the domain into the desired state. 6 | 7 | .DESCRIPTION 8 | Brings all network shares on all DCs in the domain into the desired state. 9 | Use Register-DCShare (or an ADMF Context) to define the desired state. 10 | 11 | .PARAMETER InputObject 12 | Individual test results to process. 13 | Only accepts the output of Test-DCShare. 14 | 15 | .PARAMETER Server 16 | The server / domain to work with. 17 | 18 | .PARAMETER Credential 19 | The credentials to use for this operation. 20 | 21 | .PARAMETER TargetServer 22 | The specific server(s) to process. 23 | If specified, only listed domain controllers will be affected. 24 | Specify the full FQDN of the server. 25 | 26 | .PARAMETER EnableException 27 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 28 | This is less user friendly, but allows catching exceptions in calling scripts. 29 | 30 | .PARAMETER Confirm 31 | If this switch is enabled, you will be prompted for confirmation before executing any operations that change state. 32 | 33 | .PARAMETER WhatIf 34 | If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run. 35 | 36 | .EXAMPLE 37 | PS C:\> Invoke-DCShare -Server contoso.com 38 | 39 | Tests all DCs in contoso.com, validating that their network shares are as configured, correcting any deviations. 40 | #> 41 | [CmdletBinding(SupportsShouldProcess = $true)] 42 | param ( 43 | [Parameter(ValueFromPipeline = $true)] 44 | $InputObject, 45 | 46 | [PSFComputer] 47 | $Server, 48 | 49 | [PSCredential] 50 | $Credential, 51 | 52 | [string[]] 53 | $TargetServer, 54 | 55 | [switch] 56 | $EnableException 57 | ) 58 | 59 | begin 60 | { 61 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential 62 | if (-not $Server -and $TargetServer) { 63 | $parameters.Server = $TargetServer | Select-Object -First 1 64 | } 65 | $parameters['Debug'] = $false 66 | Assert-ADConnection @parameters -Cmdlet $PSCmdlet 67 | Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet 68 | Assert-Configuration -Type Shares -Cmdlet $PSCmdlet 69 | Set-DCDomainContext @parameters 70 | 71 | $cimCred = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential 72 | 73 | $cimSessions = @{ } 74 | } 75 | process 76 | { 77 | if ($TargetServer) { $parameters.TargetServer = $TargetServer } 78 | if (-not $InputObject) { $InputObject = Test-DCShare @parameters } 79 | 80 | foreach ($testItem in $InputObject) 81 | { 82 | # Catch invalid input - can only process test results 83 | if ($testItem.PSObject.TypeNames -notcontains 'DCManagement.Share.TestResult') 84 | { 85 | Stop-PSFFunction -String 'General.Invalid.Input' -StringValues 'Test-DCShare', $testItem -Target $testItem -Continue -EnableException $EnableException 86 | } 87 | 88 | if (-not $cimSessions[$testItem.Server]) 89 | { 90 | try { $cimSessions[$testItem.Server] = New-CimSession -ComputerName $testItem.Server @cimCred -ErrorAction Stop } 91 | catch { Stop-PSFFunction -String 'Invoke-DCShare.Access.Error' -StringValues $testItem.Server -Target $testItem -Continue -EnableException $EnableException -ErrorRecord $_ } 92 | } 93 | $cimSession = $cimSessions[$testItem.Server] 94 | 95 | switch ($testItem.Type) 96 | { 97 | #region New Share 98 | 'New' 99 | { 100 | $newShareParam = @{ 101 | CimSession = $cimSession 102 | Name = $testItem.Configuration.Name 103 | Path = $testItem.Configuration.Path 104 | ErrorAction = 'Stop' 105 | WhatIf = $false 106 | Confirm = $false 107 | } 108 | if ($testItem.Configuration.Description) { $newShareParam.Description = $testItem.Configuration.Description } 109 | $grantParam = @{ 110 | ComputerName = $testItem.Server 111 | Name = $testItem.Configuration.Name 112 | } 113 | $grantParam += $cimCred 114 | 115 | Invoke-PSFProtectedCommand -ActionString 'Invoke-DCShare.Share.Create' -ActionStringValues $testItem.Identity -Target $testItem -ScriptBlock { 116 | $null = New-SmbShare @newShareParam 117 | foreach ($identity in $testItem.Configuration.FullAccess) { Grant-ShareAccess @grantParam -Identity $identity -AccessRight Full } 118 | foreach ($identity in $testItem.Configuration.WriteAccess) { Grant-ShareAccess @grantParam -Identity $identity -AccessRight Change } 119 | foreach ($identity in $testItem.Configuration.ReadAccess) { Grant-ShareAccess @grantParam -Identity $identity -AccessRight Read } 120 | } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue 121 | } 122 | #endregion New Share 123 | #region Update 124 | 'Update' 125 | { 126 | if ($testItem.Changed -contains 'Path') 127 | { 128 | $newShareParam = @{ 129 | CimSession = $cimSession 130 | Name = $testItem.Configuration.Name 131 | Path = $testItem.Configuration.Path 132 | ErrorAction = 'Stop' 133 | WhatIf = $false 134 | Confirm = $false 135 | } 136 | if ($testItem.Configuration.Description) { $newShareParam.Description = $testItem.Configuration.Description } 137 | $grantParam = @{ 138 | ComputerName = $testItem.Server 139 | Name = $testItem.Configuration.Name 140 | } 141 | $grantParam += $cimCred 142 | 143 | Invoke-PSFProtectedCommand -ActionString 'Invoke-DCShare.Share.Migrate' -ActionStringValues $testItem.Identity -Target $testItem -ScriptBlock { 144 | $null = Remove-SmbShare -Name $testItem.Configuration.Name -CimSession $cimSession 145 | $null = New-SmbShare @newShareParam 146 | foreach ($identity in $testItem.Configuration.FullAccess) { Grant-ShareAccess @grantParam -Identity $identity -AccessRight Full } 147 | foreach ($identity in $testItem.Configuration.WriteAccess) { Grant-ShareAccess @grantParam -Identity $identity -AccessRight Change } 148 | foreach ($identity in $testItem.Configuration.ReadAccess) { Grant-ShareAccess @grantParam -Identity $identity -AccessRight Read } 149 | } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue 150 | } 151 | else 152 | { 153 | $setShareParam = @{ 154 | CimSession = $cimSession 155 | Name = $testItem.Configuration.Name 156 | ErrorAction = 'Stop' 157 | WhatIf = $false 158 | Confirm = $false 159 | } 160 | if ($testItem.Configuration.Description) { $setShareParam.Description = $testItem.Configuration.Description } 161 | 162 | Invoke-PSFProtectedCommand -ActionString 'Invoke-DCShare.Share.Update' -ActionStringValues $testItem.Identity -Target $testItem -ScriptBlock { 163 | Set-SmbShare @setShareParam 164 | } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue 165 | } 166 | } 167 | #endregion Update 168 | #region Update Access Rules 169 | 'AccessUpdate' 170 | { 171 | foreach ($accessEntry in $testItem.Changed) 172 | { 173 | Invoke-PSFProtectedCommand -ActionString 'Invoke-DCShare.Share.UpdateAccess' -ActionStringValues $testItem.Identity, $accessEntry.Action, $accessEntry.Identity, $accessEntry.AccessRight -Target $testItem -ScriptBlock { 174 | if ($accessEntry.Action -eq 'Add') { Grant-ShareAccess -ComputerName $testItem.Server @cimCred -Name $testItem.Configuration.Name -Identity $accessEntry.Identity -AccessRight $accessEntry.AccessRight } 175 | else { Revoke-ShareAccess -ComputerName $testItem.Server @cimCred -Name $testItem.Configuration.Name -Identity $accessEntry.Identity -AccessRight $accessEntry.AccessRight } 176 | } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue 177 | } 178 | } 179 | #endregion Update Access Rules 180 | #region Delete Share 181 | 'Delete' 182 | { 183 | Invoke-PSFProtectedCommand -ActionString 'Invoke-DCShare.Share.Delete' -ActionStringValues $testItem.Identity -Target $testItem -ScriptBlock { 184 | $null = Remove-SmbShare -Name $testItem.ADObject.Name -CimSession $cimSession -WhatIf:$false -Confirm:$false 185 | } -EnableException $EnableException -PSCmdlet $PSCmdlet -Continue 186 | } 187 | #endregion Delete Share 188 | } 189 | } 190 | } 191 | end 192 | { 193 | $cimSessions.Values | Remove-CimSession -ErrorAction Ignore -Confirm:$false -WhatIf:$false 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /DCManagement/functions/shares/Register-DCShare.ps1: -------------------------------------------------------------------------------- 1 | function Register-DCShare { 2 | <# 3 | .SYNOPSIS 4 | Registers an SMB share that should exist on DCs. 5 | 6 | .DESCRIPTION 7 | Registers an SMB share that should exist on DCs. 8 | 9 | .PARAMETER Name 10 | The name of the share. 11 | Supports string resolution. 12 | 13 | .PARAMETER Path 14 | The path the share points to. 15 | Supports string resolution. 16 | 17 | .PARAMETER Description 18 | The description of the share. 19 | Supports string resolution. 20 | 21 | .PARAMETER FullAccess 22 | The principals to grant full access to. 23 | Supports string resolution. 24 | 25 | .PARAMETER WriteAccess 26 | The principals to grant write access to. 27 | Supports string resolution. 28 | 29 | .PARAMETER ReadAccess 30 | The principals to grant read access to. 31 | Supports string resolution. 32 | 33 | .PARAMETER AccessMode 34 | How share access rules are processed. 35 | Supports three configurations: 36 | - Constrained: The default access mode, will remove any excess access rules. 37 | - Additive: Ignore any access rules already on the share, even if not configured 38 | - Defined: Ignore any access rules already on the share, even if not configured UNLESS the identity on those rules has an access level defined for it. 39 | 40 | .PARAMETER ServerRole 41 | What domain controller to apply this to: 42 | - All: All DCs in the enterprise 43 | - FSMO: Only DCs that have any FSMO role 44 | - PDC: Only the PDCEmulator 45 | 46 | .PARAMETER ContextName 47 | The name of the context defining the setting. 48 | This allows determining the configuration set that provided this setting. 49 | Used by the ADMF, available to any other configuration management solution. 50 | 51 | .EXAMPLE 52 | PS C:\> Get-Content .\shares.json | ConvertFrom-Json | Write-Output | Register-DCShare 53 | 54 | Reads all share definitions from json and imports the definitions. 55 | #> 56 | [CmdletBinding()] 57 | Param ( 58 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 59 | [string] 60 | $Name, 61 | 62 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 63 | [string] 64 | $Path, 65 | 66 | [Parameter(ValueFromPipelineByPropertyName = $true)] 67 | [AllowEmptyCollection()] 68 | [string] 69 | $Description, 70 | 71 | [Parameter(ValueFromPipelineByPropertyName = $true)] 72 | [AllowEmptyCollection()] 73 | [string[]] 74 | $FullAccess, 75 | 76 | [Parameter(ValueFromPipelineByPropertyName = $true)] 77 | [AllowEmptyCollection()] 78 | [string[]] 79 | $WriteAccess, 80 | 81 | [Parameter(ValueFromPipelineByPropertyName = $true)] 82 | [AllowEmptyCollection()] 83 | [string[]] 84 | $ReadAccess, 85 | 86 | [Parameter(ValueFromPipelineByPropertyName = $true)] 87 | [ValidateSet('Constrained', 'Additive', 'Defined')] 88 | [string] 89 | $AccessMode = 'Constrained', 90 | 91 | [Parameter(ValueFromPipelineByPropertyName = $true)] 92 | [ValidateSet('All', 'FSMO', 'PDC')] 93 | [string] 94 | $ServerRole = 'All', 95 | 96 | [string] 97 | $ContextName = '' 98 | ) 99 | 100 | process { 101 | $script:shares[$Name] = [PSCustomObject]@{ 102 | PSTypeName = 'DCManagement.Share' 103 | Name = $Name 104 | Path = $Path 105 | Description = $Description 106 | FullAccess = $FullAccess 107 | WriteAccess = $WriteAccess 108 | ReadAccess = $ReadAccess 109 | AccessMode = $AccessMode 110 | ServerRole = $ServerRole 111 | ContextName = $ContextName 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /DCManagement/functions/shares/Test-DCShare.ps1: -------------------------------------------------------------------------------- 1 | function Test-DCShare 2 | { 3 | <# 4 | .SYNOPSIS 5 | Tests all DCs in the target domain for share compliance. 6 | 7 | .DESCRIPTION 8 | Tests all DCs in the target domain, by comparing existing shares with the list of defined shares. 9 | Use Register-DCShare (or an ADMF Context) to define shares. 10 | 11 | .PARAMETER Server 12 | The server / domain to work with. 13 | 14 | .PARAMETER Credential 15 | The credentials to use for this operation. 16 | 17 | .PARAMETER TargetServer 18 | The specific server(s) to process. 19 | If specified, only listed domain controllers will be affected. 20 | Specify the full FQDN of the server. 21 | 22 | .PARAMETER EnableException 23 | This parameters disables user-friendly warnings and enables the throwing of exceptions. 24 | This is less user friendly, but allows catching exceptions in calling scripts. 25 | 26 | .EXAMPLE 27 | PS C:\> Test-DCShare -Server contoso.com 28 | 29 | Tests all DCs in the domain contoso.com, whether their shares are as configured. 30 | #> 31 | [CmdletBinding()] 32 | Param ( 33 | [PSFComputer] 34 | $Server, 35 | 36 | [PSCredential] 37 | $Credential, 38 | 39 | [string[]] 40 | $TargetServer, 41 | 42 | [switch] 43 | $EnableException 44 | ) 45 | 46 | begin 47 | { 48 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential 49 | if (-not $Server -and $TargetServer) { 50 | $parameters.Server = $TargetServer | Select-Object -First 1 51 | } 52 | $parameters['Debug'] = $false 53 | Assert-ADConnection @parameters -Cmdlet $PSCmdlet 54 | Invoke-PSFCallback -Data $parameters -EnableException $true -PSCmdlet $PSCmdlet 55 | Assert-Configuration -Type Shares -Cmdlet $PSCmdlet 56 | Set-DCDomainContext @parameters 57 | 58 | $domainControllers = Get-DomainController @parameters 59 | $cimCred = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential 60 | 61 | #region Utility Functions 62 | function ConvertFrom-ShareConfiguration 63 | { 64 | [CmdletBinding()] 65 | param ( 66 | [Parameter(ValueFromPipeline = $true)] 67 | $ShareConfiguration, 68 | 69 | [hashtable] 70 | $Parameters 71 | ) 72 | 73 | process 74 | { 75 | $cfgHash = $ShareConfiguration | ConvertTo-PSFHashtable 76 | $cfgHash.Name = $cfgHash.Name | Resolve-String -ArgumentList $Parameters 77 | $cfgHash.Path = $cfgHash.Path | Resolve-String -ArgumentList $Parameters 78 | $cfgHash.Description = $cfgHash.Description | Resolve-String -ArgumentList $Parameters 79 | $cfgHash.AccessIdentityIntegrity = $true 80 | $cfgHash.FullAccess = foreach ($entry in $cfgHash.FullAccess) 81 | { 82 | try { ($entry | Resolve-String -ArgumentList $Parameters | Resolve-Principal @Parameters -OutputType SID -ErrorAction Stop) -as [string] } 83 | catch 84 | { 85 | Write-PSFMessage -Level Warning -String 'Test-DCShare.Identity.Resolution.Failed' -StringValues $entry -Target $ShareConfiguration 86 | $cfgHash.AccessIdentityIntegrity = $false 87 | } 88 | } 89 | $cfgHash.WriteAccess = foreach ($entry in $cfgHash.WriteAccess) 90 | { 91 | try { ($entry | Resolve-String -ArgumentList $Parameters | Resolve-Principal @Parameters -OutputType SID -ErrorAction Stop) -as [string] } 92 | catch 93 | { 94 | Write-PSFMessage -Level Warning -String 'Test-DCShare.Identity.Resolution.Failed' -StringValues $entry -Target $ShareConfiguration 95 | $cfgHash.AccessIdentityIntegrity = $false 96 | } 97 | } 98 | $cfgHash.ReadAccess = foreach ($entry in $cfgHash.ReadAccess) 99 | { 100 | try { ($entry | Resolve-String -ArgumentList $Parameters | Resolve-Principal @Parameters -OutputType SID -ErrorAction Stop) -as [string] } 101 | catch 102 | { 103 | Write-PSFMessage -Level Warning -String 'Test-DCShare.Identity.Resolution.Failed' -StringValues $entry -Target $ShareConfiguration 104 | $cfgHash.AccessIdentityIntegrity = $false 105 | } 106 | } 107 | [pscustomobject]$cfgHash 108 | } 109 | } 110 | 111 | function Compare-ShareAccess 112 | { 113 | [CmdletBinding()] 114 | param ( 115 | $Configuration, 116 | 117 | $ShareAccess, 118 | 119 | [hashtable] 120 | $Parameters 121 | ) 122 | 123 | $access = @{ 124 | Full = @() 125 | Change = @() 126 | Read = @() 127 | } 128 | foreach ($accessItem in $ShareAccess) 129 | { 130 | $access["$($accessItem.AccessRight)"] += ($accessItem.AccountName | Resolve-Principal @Parameters -OutputType SID) -as [string] 131 | } 132 | 133 | #region Compare Defined with current state 134 | foreach ($entity in $Configuration.FullAccess) 135 | { 136 | if ($entity -notin $access.Full) { New-AccessChange -AccessRight Full -Identity $entity -Action Add } 137 | } 138 | foreach ($entity in $Configuration.WriteAccess) 139 | { 140 | if ($entity -notin $access.Change) { New-AccessChange -AccessRight Change -Identity $entity -Action Add } 141 | } 142 | foreach ($entity in $Configuration.ReadAccess) 143 | { 144 | if ($entity -notin $access.Read) { New-AccessChange -AccessRight Read -Identity $entity -Action Add } 145 | } 146 | #endregion Compare Defined with current state 147 | 148 | # If we will not remove any rights, no point inspecting the existing access rights 149 | if ($Configuration.AccessMode -eq 'Additive') { return } 150 | 151 | #region Compare current with defined state 152 | #region Full Access 153 | foreach ($entity in $access.Full) 154 | { 155 | if ($entity -notin $Configuration.FullAccess) 156 | { 157 | if ($Configuration.AccessMode -eq 'Constrained') 158 | { 159 | New-AccessChange -AccessRight Full -Identity $entity -Action Remove 160 | continue 161 | } 162 | 163 | if ($entity -notin $Configuration.WriteAccess -and $entity -notin $Configuration.ReadAccess) { continue } 164 | New-AccessChange -AccessRight Full -Identity $entity -Action Remove 165 | } 166 | } 167 | #endregion Full Access 168 | #region Write Access 169 | foreach ($entity in $access.Change) 170 | { 171 | if ($entity -notin $Configuration.WriteAccess) 172 | { 173 | if ($Configuration.AccessMode -eq 'Constrained') 174 | { 175 | New-AccessChange -AccessRight Change -Identity $entity -Action Remove 176 | continue 177 | } 178 | 179 | if ($entity -notin $Configuration.FullAccess -and $entity -notin $Configuration.ReadAccess) { continue } 180 | New-AccessChange -AccessRight Change -Identity $entity -Action Remove 181 | } 182 | } 183 | #endregion Write Access 184 | #region Read Access 185 | foreach ($entity in $access.Read) 186 | { 187 | if ($entity -notin $Configuration.ReadAccess) 188 | { 189 | if ($Configuration.AccessMode -eq 'Constrained') 190 | { 191 | New-AccessChange -AccessRight Read -Identity $entity -Action Remove 192 | continue 193 | } 194 | 195 | if ($entity -notin $Configuration.FullAccess -and $entity -notin $Configuration.WriteAccess) { continue } 196 | New-AccessChange -AccessRight Read -Identity $entity -Action Remove 197 | } 198 | } 199 | #endregion Read Access 200 | #endregion Compare current with defined state 201 | } 202 | 203 | function New-AccessChange 204 | { 205 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 206 | [CmdletBinding()] 207 | param ( 208 | [ValidateSet('Full','Change','Read')] 209 | [string] 210 | $AccessRight, 211 | 212 | [string] 213 | $Identity, 214 | 215 | [ValidateSet('Add','Remove')] 216 | [string] 217 | $Action 218 | ) 219 | 220 | [PSCustomObject]@{ 221 | PSTypeName = 'DCManagement.Share.AccessChange' 222 | Action = $Action 223 | AccessRight = $AccessRight 224 | Identity = $Identity 225 | } 226 | } 227 | #endregion Utility Functions 228 | } 229 | process 230 | { 231 | foreach ($domainController in $domainControllers) 232 | { 233 | if ($TargetServer -and $domainController.Name -notin $TargetServer) { continue } 234 | $results = @{ 235 | ObjectType = 'Share' 236 | Server = $domainController.Name 237 | } 238 | 239 | Write-PSFMessage -String 'Test-DCShare.Processing' -StringValues $domainController.Name -Tag DCTarget 240 | try { $cimSession = New-CimSession -ComputerName $domainController.Name @cimCred -ErrorAction Stop } 241 | catch { Stop-PSFFunction -String 'Test-DCShare.CimSession.Failed' -StringValues $domainController.Name -EnableException $EnableException -Cmdlet $PSCmdlet -Continue -Target $domainController.Name -ErrorRecord $_ } 242 | $shareConfigurations = Get-DCShare | Where-Object { 243 | $_.ServerRole -eq 'ALL' -or 244 | ($_.ServerRole -eq 'FSMO' -and $domainController.IsFSMO) -or 245 | ($_.ServerRole -eq 'PDC' -and $domainController.IsPDCEmulator) 246 | } | ConvertFrom-ShareConfiguration -Parameters $parameters 247 | 248 | $shares = Get-SmbShare -CimSession $cimSession -IncludeHidden | Add-Member -MemberType NoteProperty -Name ComputerName -Value $domainController.Name -Force -PassThru 249 | 250 | foreach ($share in $shares) 251 | { 252 | $sResults = $results.Clone() 253 | $sResults.Identity = '\\{0}\{1}' -f $share.ComputerName, $share.Name 254 | $sResults.ADObject = $share 255 | 256 | #region Share exists, but is not defined 257 | if ($share.Name -notin $shareConfigurations.Name) 258 | { 259 | # The special builtin shares will not trigger delete actions 260 | if ($share.Special) { continue } 261 | if ($share.Name -in 'NETLOGON','SYSVOL') { continue } 262 | 263 | New-Testresult @sResults -Type Delete 264 | continue 265 | } 266 | #endregion Share exists, but is not defined 267 | 268 | $configuration = $shareConfigurations | Where-Object Name -EQ $share.Name 269 | $sResults.Configuration = $configuration 270 | 271 | #region Handle Property Settings 272 | [System.Collections.ArrayList]$changes = @() 273 | Compare-Property -Configuration $configuration -ADObject $share -Changes $changes -Property Path 274 | Compare-Property -Configuration $configuration -ADObject $share -Changes $changes -Property Description 275 | 276 | if ($changes) 277 | { 278 | New-Testresult @sResults -Type Update -Changed $changes.ToArray() 279 | } 280 | #endregion Handle Property Settings 281 | 282 | #region Delegation 283 | if (-not $configuration.AccessIdentityIntegrity) 284 | { 285 | Write-PSFMessage -Level Warning -String 'Test-DCShare.Access.IntegrityError' -StringValues $share.Name, $domainController.Name -Tag panic, error, fail 286 | continue 287 | } 288 | $access = Get-SmbShareAccess -CimSession $cimSession -Name $share.Name 289 | $delta = Compare-ShareAccess -Configuration $configuration -ShareAccess $access -Parameters $parameters 290 | if ($delta) 291 | { 292 | New-Testresult @sResults -Type AccessUpdate -Changed $delta 293 | } 294 | #endregion Delegation 295 | } 296 | 297 | foreach ($cfgShare in $shareConfigurations) 298 | { 299 | if ($cfgShare.Name -in $shares.Name) { continue } 300 | 301 | New-TestResult @results -Type New -Identity "\\$($domainController.Name)\$($cfgShare.Name)" -Configuration $cfgShare 302 | } 303 | 304 | Remove-CimSession -CimSession $cimSession -ErrorAction Ignore -WhatIf:$false -Confirm:$false 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /DCManagement/functions/shares/Unregister-DCShare.ps1: -------------------------------------------------------------------------------- 1 | function Unregister-DCShare 2 | { 3 | <# 4 | .SYNOPSIS 5 | Removes a specific share from the list of registered shares. 6 | 7 | .DESCRIPTION 8 | Removes a specific share from the list of registered shares. 9 | 10 | .PARAMETER Name 11 | The exact name of the share to unregister. 12 | 13 | .EXAMPLE 14 | PS C:\> Get-DCShare | Unregister-DCShare 15 | 16 | Clears all registered shares. 17 | #> 18 | [CmdletBinding()] 19 | Param ( 20 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 21 | [string[]] 22 | $Name 23 | ) 24 | 25 | process 26 | { 27 | foreach ($nameString in $Name) { 28 | $script:shares.Remove($nameString) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DCManagement/functions/system/Clear-DCConfiguration.ps1: -------------------------------------------------------------------------------- 1 | function Clear-DCConfiguration 2 | { 3 | <# 4 | .SYNOPSIS 5 | Resets all DC specific configuration settings. 6 | 7 | .DESCRIPTION 8 | Resets all DC specific configuration settings. 9 | 10 | .EXAMPLE 11 | PS C:\> Clear-DCConfiguration 12 | 13 | Resets all DC specific configuration settings. 14 | #> 15 | [CmdletBinding()] 16 | Param ( 17 | 18 | ) 19 | 20 | process 21 | { 22 | . "$script:ModuleRoot\internal\scripts\variables.ps1" 23 | } 24 | } -------------------------------------------------------------------------------- /DCManagement/functions/system/Set-DCDomainContext.ps1: -------------------------------------------------------------------------------- 1 | function Set-DCDomainContext 2 | { 3 | <# 4 | .SYNOPSIS 5 | Updates the domain settings for string replacement. 6 | 7 | .DESCRIPTION 8 | Updates the domain settings for string replacement. 9 | 10 | .PARAMETER Server 11 | The server / domain to work with. 12 | 13 | .PARAMETER Credential 14 | The credentials to use for this operation. 15 | 16 | .EXAMPLE 17 | PS C:\> Set-DCDomainContext @parameters 18 | 19 | Updates the current domain context 20 | #> 21 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 22 | [CmdletBinding()] 23 | param ( 24 | [PSFComputer] 25 | $Server, 26 | 27 | [PSCredential] 28 | $Credential 29 | ) 30 | 31 | begin 32 | { 33 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential 34 | $parameters['Debug'] = $false 35 | } 36 | process 37 | { 38 | $domainObject = Get-ADDomain @parameters 39 | $forestObject = Get-ADForest @parameters 40 | if ($forestObject.RootDomain -eq $domainObject.DNSRoot) 41 | { 42 | $forestRootDomain = $domainObject 43 | $forestRootSID = $forestRootDomain.DomainSID.Value 44 | } 45 | else 46 | { 47 | try 48 | { 49 | $cred = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential 50 | $forestRootDomain = Get-ADDomain @cred -Server $forestObject.RootDomain -ErrorAction Stop 51 | $forestRootSID = $forestRootDomain.DomainSID.Value 52 | } 53 | catch 54 | { 55 | $forestRootDomain = [PSCustomObject]@{ 56 | Name = $forestObject.RootDomain.Split(".", 2)[0] 57 | DNSRoot = $forestObject.RootDomain 58 | DistinguishedName = 'DC={0}' -f ($forestObject.RootDomain.Split(".") -join ",DC=") 59 | } 60 | $forestRootSID = (Get-ADObject @parameters -SearchBase "CN=System,$($domainObject.DistinguishedName)" -SearchScope OneLevel -LDAPFilter "(&(objectClass=trustedDomain)(trustPartner=$($forestObject.RootDomain)))" -Properties securityIdentifier).securityIdentifier.Value 61 | } 62 | } 63 | 64 | Register-StringMapping -Name '%DomainName%' -Value $domainObject.Name 65 | Register-StringMapping -Name '%DomainNetBIOSName%' -Value $domainObject.NetbiosName 66 | Register-StringMapping -Name '%DomainFqdn%' -Value $domainObject.DNSRoot 67 | Register-StringMapping -Name '%DomainDN%' -Value $domainObject.DistinguishedName 68 | Register-StringMapping -Name '%DomainSID%' -Value $domainObject.DomainSID.Value 69 | Register-StringMapping -Name '%RootDomainName%' -Value $forestRootDomain.Name 70 | Register-StringMapping -Name '%RootDomainFqdn%' -Value $forestRootDomain.DNSRoot 71 | Register-StringMapping -Name '%RootDomainDN%' -Value $forestRootDomain.DistinguishedName 72 | Register-StringMapping -Name '%RootDomainSID%' -Value $forestRootSID 73 | Register-StringMapping -Name '%ForestFqdn%' -Value $forestObject.Name 74 | } 75 | } -------------------------------------------------------------------------------- /DCManagement/internal/configurations/configuration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This is an example configuration file 3 | 4 | By default, it is enough to have a single one of them, 5 | however if you have enough configuration settings to justify having multiple copies of it, 6 | feel totally free to split them into multiple files. 7 | #> 8 | 9 | <# 10 | # Example Configuration 11 | Set-PSFConfig -Module 'DCManagement' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'" 12 | #> 13 | 14 | Set-PSFConfig -Module 'DCManagement' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." 15 | Set-PSFConfig -Module 'DCManagement' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." 16 | 17 | Set-PSFConfig -Module 'DCManagement' -Name 'Defaults.NoDNS' -Value $false -Validation bool -Initialize -Description 'Default value for "NoDNS" parameter when creating a new forest' 18 | Set-PSFConfig -Module 'DCManagement' -Name 'Defaults.NoReboot' -Value $false -Validation bool -Initialize -Description 'Default value for "NoReboot" parameter when creating a new forest' 19 | Set-PSFConfig -Module 'DCManagement' -Name 'Defaults.LogPath' -Value 'C:\Windows\NTDS' -Validation string -Initialize -Description 'Default value for "LogPath" parameter when creating a new forest' 20 | Set-PSFConfig -Module 'DCManagement' -Name 'Defaults.SysvolPath' -Value 'C:\Windows\SYSVOL' -Validation string -Initialize -Description 'Default value for "SysvolPath" parameter when creating a new forest' 21 | Set-PSFConfig -Module 'DCManagement' -Name 'Defaults.DatabasePath' -Value 'C:\Windows\NTDS' -Validation string -Initialize -Description 'Default value for "DatabasePath" parameter when creating a new forest' -------------------------------------------------------------------------------- /DCManagement/internal/configurations/readme.md: -------------------------------------------------------------------------------- 1 | # Configurations 2 | 3 | Through the `PSFramework` you have a simple method that allows you to ... 4 | 5 | - Publish settings 6 | - With onboard documentation 7 | - Input validation 8 | - Scripts that run on change of settings 9 | - That can be discovered and updated by the user 10 | - That can be administrated by policy & DSC 11 | 12 | The configuration system is a bit too complex to describe in a help file, you can however visit us at http://psframework.org for detailed guidance. 13 | 14 | An example can be seen in the attached ps1 file -------------------------------------------------------------------------------- /DCManagement/internal/functions/Assert-ADConnection.ps1: -------------------------------------------------------------------------------- 1 | function Assert-ADConnection 2 | { 3 | <# 4 | .SYNOPSIS 5 | Ensures connection to AD is possible before performing actions. 6 | 7 | .DESCRIPTION 8 | Ensures connection to AD is possible before performing actions. 9 | Should be the first things all commands connecting to AD should call. 10 | Do this before invoking callbacks, as the configuration change becomes pointless if the forest is unavailable to begin with, 11 | 12 | .PARAMETER Server 13 | The server / domain to work with. 14 | 15 | .PARAMETER Credential 16 | The credentials to use for this operation. 17 | 18 | .PARAMETER Cmdlet 19 | The $PSCmdlet variable of the calling command. 20 | Used to safely terminate the calling command in case of failure. 21 | 22 | .EXAMPLE 23 | PS C:\> Assert-ADConnection @parameters -Cmdlet $PSCmdlet 24 | 25 | Kills the calling command if AD is not available. 26 | #> 27 | [CmdletBinding()] 28 | Param ( 29 | [PSFComputer] 30 | $Server, 31 | 32 | [PSCredential] 33 | $Credential, 34 | 35 | [Parameter(Mandatory = $true)] 36 | [System.Management.Automation.PSCmdlet] 37 | $Cmdlet 38 | ) 39 | 40 | begin 41 | { 42 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential 43 | } 44 | process 45 | { 46 | # A domain being unable to retrieve its own object can really only happen if the service is down 47 | try { $null = Get-ADDomain @parameters -ErrorAction Stop } 48 | catch { 49 | Write-PSFMessage -Level Warning -String 'Assert-ADConnection.Failed' -StringValues $Server -Tag 'failed' -ErrorRecord $_ 50 | $Cmdlet.ThrowTerminatingError($_) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DCManagement/internal/functions/Assert-Configuration.ps1: -------------------------------------------------------------------------------- 1 | function Assert-Configuration 2 | { 3 | <# 4 | .SYNOPSIS 5 | Ensures a set of configuration settings has been provided for the specified setting type. 6 | 7 | .DESCRIPTION 8 | Ensures a set of configuration settings has been provided for the specified setting type. 9 | This maps to the configuration variables defined in variables.ps1 10 | Note: Not ALL variables defined in that file should be mapped, only those storing individual configuration settings! 11 | 12 | .PARAMETER Type 13 | The setting type to assert. 14 | 15 | .PARAMETER Cmdlet 16 | The $PSCmdlet variable of the calling command. 17 | Used to safely terminate the calling command in case of failure. 18 | 19 | .EXAMPLE 20 | PS C:\> Assert-Configuration -Type Users 21 | 22 | Asserts, that users have already been specified. 23 | #> 24 | [CmdletBinding()] 25 | Param ( 26 | [Parameter(Mandatory = $true)] 27 | [string] 28 | $Type, 29 | 30 | [Parameter(Mandatory = $true)] 31 | [System.Management.Automation.PSCmdlet] 32 | $Cmdlet 33 | ) 34 | 35 | process 36 | { 37 | if ((Get-Variable -Name $Type -Scope Script -ValueOnly -ErrorAction Ignore).Count -gt 0) { return } 38 | 39 | Write-PSFMessage -Level Warning -String 'Assert-Configuration.NotConfigured' -StringValues $Type -FunctionName $Cmdlet.CommandRuntime 40 | 41 | $exception = New-Object System.Data.DataException("No configuration data provided for: $Type") 42 | $errorID = 'NotConfigured' 43 | $category = [System.Management.Automation.ErrorCategory]::NotSpecified 44 | $recordObject = New-Object System.Management.Automation.ErrorRecord($exception, $errorID, $category, $Type) 45 | $cmdlet.ThrowTerminatingError($recordObject) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /DCManagement/internal/functions/Compare-Property.ps1: -------------------------------------------------------------------------------- 1 | function Compare-Property 2 | { 3 | <# 4 | .SYNOPSIS 5 | Helper function simplifying the changes processing. 6 | 7 | .DESCRIPTION 8 | Helper function simplifying the changes processing. 9 | 10 | .PARAMETER Property 11 | The property to use for comparison. 12 | 13 | .PARAMETER Configuration 14 | The object that was used to define the desired state. 15 | 16 | .PARAMETER ADObject 17 | The AD Object containing the actual state. 18 | 19 | .PARAMETER Changes 20 | An arraylist where changes get added to. 21 | The content of -Property will be added if the comparison fails. 22 | 23 | .PARAMETER Resolve 24 | Whether the value on the configured object's property should be string-resolved. 25 | 26 | .PARAMETER ADProperty 27 | The property on the ad object to use for the comparison. 28 | If this parameter is not specified, it uses the value from -Property. 29 | 30 | .EXAMPLE 31 | PS C:\> Compare-Property -Property Description -Configuration $ouDefinition -ADObject $adObject -Changes $changes -Resolve 32 | 33 | Compares the description on the configuration object (after resolving it) with the one on the ADObject and adds to $changes if they are inequal. 34 | #> 35 | 36 | [CmdletBinding()] 37 | param ( 38 | [Parameter(Mandatory = $true)] 39 | [string] 40 | $Property, 41 | 42 | [Parameter(Mandatory = $true)] 43 | [object] 44 | $Configuration, 45 | 46 | [Parameter(Mandatory = $true)] 47 | [object] 48 | $ADObject, 49 | 50 | [Parameter(Mandatory = $true)] 51 | [AllowEmptyCollection()] 52 | [System.Collections.ArrayList] 53 | $Changes, 54 | 55 | [switch] 56 | $Resolve, 57 | 58 | [string] 59 | $ADProperty 60 | ) 61 | 62 | begin 63 | { 64 | if (-not $ADProperty) { $ADProperty = $Property } 65 | } 66 | process 67 | { 68 | $propValue = $Configuration.$Property 69 | if ($Resolve) { $propValue = $propValue | Resolve-String } 70 | 71 | if (($propValue -is [System.Collections.ICollection]) -and ($ADObject.$ADProperty -is [System.Collections.ICollection])) 72 | { 73 | if (Compare-Object $propValue $ADObject.$ADProperty) 74 | { 75 | $null = $Changes.Add($Property) 76 | } 77 | } 78 | elseif ($propValue -ne $ADObject.$ADProperty) 79 | { 80 | $null = $Changes.Add($Property) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /DCManagement/internal/functions/Get-DomainController.ps1: -------------------------------------------------------------------------------- 1 | function Get-DomainController { 2 | <# 3 | .SYNOPSIS 4 | Returns a list of domain controllers with their respective FSMO state. 5 | 6 | .DESCRIPTION 7 | Returns a list of domain controllers with their respective FSMO state. 8 | 9 | .PARAMETER Server 10 | The server / domain to work with. 11 | 12 | .PARAMETER Credential 13 | The credentials to use for this operation. 14 | 15 | .EXAMPLE 16 | PS C:\> Get-DomainController 17 | 18 | List all DCs of the current domain with their respective FSMO membership 19 | #> 20 | [CmdletBinding()] 21 | Param ( 22 | [PSFComputer] 23 | $Server, 24 | 25 | [pscredential] 26 | $Credential 27 | ) 28 | 29 | begin { 30 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Server, Credential 31 | } 32 | process { 33 | $forest = Get-ADForest @parameters 34 | $domain = Get-ADDomain @parameters 35 | 36 | $fsmo = $forest.DomainNamingMaster, $forest.SchemaMaster, $domain.PDCEmulator, $domain.InfrastructureMaster, $domain.RIDMaster 37 | 38 | $domainControllers = Get-ADComputer @parameters -LdapFilter '(primaryGroupID=516)' 39 | 40 | foreach ($controller in $domainControllers) { 41 | [PSCustomObject]@{ 42 | Name = $controller.DNSHostName 43 | IsFSMO = $controller.DNSHostName -in $fsmo 44 | IsPDCEmulator = $domain.PDCEmulator -eq $controller.DNSHostName 45 | IsDomainNamingMaster = $forest.DomainNamingMaster -eq $controller.DNSHostName 46 | IsSchemaMaster = $forest.SchemaMaster -eq $controller.DNSHostName 47 | IsInfrastructureMaster = $domain.InfrastructureMaster -eq $controller.DNSHostName 48 | IsRIDMaster = $domain.RIDMaster -eq $controller.DNSHostName 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /DCManagement/internal/functions/Grant-ShareAccess.ps1: -------------------------------------------------------------------------------- 1 | function Grant-ShareAccess 2 | { 3 | <# 4 | .SYNOPSIS 5 | Grants access to a network share. 6 | 7 | .DESCRIPTION 8 | Grants access to a network share. 9 | User must be specified as a SID. 10 | This command uses PowerShell remoting to access the target computer. 11 | 12 | .PARAMETER ComputerName 13 | The name of the server to operate against. 14 | 15 | .PARAMETER Credential 16 | The credentials to use for authentication. 17 | 18 | .PARAMETER Name 19 | The name of the share to modifiy. 20 | 21 | .PARAMETER Identity 22 | The SID of the user to grant permissions to. 23 | 24 | .PARAMETER AccessRight 25 | The rights of the user that has permissions granted. 26 | 27 | .EXAMPLE 28 | PS C:\> Grant-ShareAccess @parameters -Name Legal -Identity S-1-5-21-584015949-955715703-1113067636-1105 -AccessRight Full 29 | 30 | Grants the specified user full access right to the share "Legal" 31 | #> 32 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWMICmdlet", "")] 33 | [CmdletBinding()] 34 | param ( 35 | [PSFComputer] 36 | $ComputerName, 37 | 38 | [PSCredential] 39 | $Credential, 40 | 41 | [string] 42 | $Name, 43 | 44 | [string] 45 | $Identity, 46 | 47 | [ValidateSet('Full', 'Change', 'Read')] 48 | [string] 49 | $AccessRight 50 | ) 51 | 52 | begin 53 | { 54 | #region Permission Grant Scriptblock 55 | $scriptblock = { 56 | param ( 57 | [Hashtable] 58 | $Data 59 | ) 60 | 61 | function Write-Result 62 | { 63 | [CmdletBinding()] 64 | param ( 65 | [switch] 66 | $Failed, 67 | 68 | [string] 69 | $State, 70 | 71 | [string] 72 | $Message 73 | ) 74 | 75 | [pscustomobject]@{ 76 | Success = (-not $Failed) 77 | State = $State 78 | Message = $Message 79 | } 80 | } 81 | 82 | 83 | $accessHash = @{ 84 | Full = 2032127 85 | Change = 1245631 86 | Read = 1179817 87 | } 88 | 89 | $sid = [System.Security.Principal.SecurityIdentifier]$Data.Identity 90 | [byte[]]$sidBytes = New-Object System.Byte[]($sid.BinaryLength) 91 | $sid.GetBinaryForm($sidBytes, 0) 92 | 93 | $trustee = (New-Object System.Management.ManagementClass('Win32_Trustee')).CreateInstance() 94 | $trustee.SID = $sidBytes 95 | $trustee.SidLength = $sid.BinaryLength 96 | $trustee.SIDString = $sid.Value 97 | 98 | $aceObject = (New-Object System.Management.ManagementClass('Win32_ACE')).CreateInstance() 99 | $aceObject.AceFlags = 0 100 | $aceObject.AceType = 0 101 | $aceObject.AccessMask = $accessHash[$Data.AccessRight] 102 | $aceObject.Trustee = $trustee 103 | 104 | try { $securitySettings = Get-WmiObject -Query ('SELECT * FROM Win32_LogicalShareSecuritySetting WHERE Name = "{0}"' -f $Data.Name) -ErrorAction Stop } 105 | catch { return Write-Result -Failed -State WMIAccess -Message $_ } 106 | $securityDescriptor = $securitySettings.GetSecurityDescriptor().Descriptor 107 | [System.Management.ManagementBaseObject[]]$accessControlList = $securityDescriptor.DACL 108 | if (-not $accessControlList) { $accessControlList = New-Object System.Management.ManagementBaseObject[](1) } 109 | else { [array]::Resize([ref]$accessControlList, ($accessControlList.Length + 1)) } 110 | $accessControlList[-1] = $aceObject 111 | $securityDescriptor.DACL = $accessControlList 112 | $result = $securitySettings.SetSecurityDescriptor($securityDescriptor) 113 | 114 | if ($result.ReturnValue -ne 0) { Write-Result -Failed -State 'FailedApply' -Message "Failed to apply with WMI code $($result.ReturnValue)" } 115 | else { Write-Result -State Success -Message 'Permissions successfully applied' } 116 | } 117 | #endregion Permission Grant Scriptblock 118 | 119 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include ComputerName, Credential 120 | } 121 | process 122 | { 123 | $data = $PSBoundParameters | ConvertTo-PSFHashtable -Include Name, Identity, AccessRight 124 | try { $results = Invoke-PSFCommand @parameters -ScriptBlock $scriptblock -ErrorAction Stop -ArgumentList $data } 125 | catch { Stop-PSFFunction -String 'Grant-ShareAccess.WinRM.Failed' -StringValues $Identity, $Name, $ComputerName -EnableException $true -ErrorRecord $_ -Target $ComputerName -Cmdlet $PSCmdlet } 126 | 127 | if (-not $results.Success) 128 | { 129 | Stop-PSFFunction -String 'Grant-ShareAccess.Execution.Failed' -StringValues $Identity, $Name, $ComputerName, $results.Status, $results.Message -EnableException $true -ErrorRecord $_ -Target $ComputerName -Cmdlet $PSCmdlet 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /DCManagement/internal/functions/New-Password.ps1: -------------------------------------------------------------------------------- 1 | function New-Password 2 | { 3 | <# 4 | .SYNOPSIS 5 | Generate a new, complex password. 6 | 7 | .DESCRIPTION 8 | Generate a new, complex password. 9 | 10 | .PARAMETER Length 11 | The length of the password calculated. 12 | Defaults to 32 13 | 14 | .PARAMETER AsSecureString 15 | Returns the password as secure string. 16 | 17 | .EXAMPLE 18 | PS C:\> New-Password 19 | 20 | Generates a new 32v character password. 21 | #> 22 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 23 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] 24 | [CmdletBinding()] 25 | Param ( 26 | [int] 27 | $Length = 32, 28 | 29 | [switch] 30 | $AsSecureString 31 | ) 32 | 33 | begin 34 | { 35 | $characters = @{ 36 | 0 = @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') 37 | 1 = @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') 38 | 2 = @(0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9) 39 | 3 = @('#','$','%','&',"'",'(',')','*','+',',','-','.','/',':',';','<','=','>','?','@') 40 | 4 = @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') 41 | 5 = @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') 42 | 6 = @(0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9) 43 | 7 = @('#','$','%','&',"'",'(',')','*','+',',','-','.','/',':',';','<','=','>','?','@') 44 | } 45 | } 46 | process 47 | { 48 | $letters = foreach ($number in (1..$Length)) { 49 | $characters[(($number % 4) + (1..4 | Get-Random))] | Get-Random 50 | } 51 | if ($AsSecureString) { $letters -join "" | ConvertTo-SecureString -AsPlainText -Force } 52 | else { $letters -join "" } 53 | } 54 | } -------------------------------------------------------------------------------- /DCManagement/internal/functions/New-Testresult.ps1: -------------------------------------------------------------------------------- 1 | function New-TestResult 2 | { 3 | <# 4 | .SYNOPSIS 5 | Generates a new test result object. 6 | 7 | .DESCRIPTION 8 | Generates a new test result object. 9 | Helper function that slims down the Test- commands. 10 | 11 | .PARAMETER ObjectType 12 | What kind of object is being processed (e.g.: User, OrganizationalUnit, Group, ...) 13 | 14 | .PARAMETER Type 15 | What kind of change needs to be performed 16 | 17 | .PARAMETER Identity 18 | Identity of the change item 19 | 20 | .PARAMETER Changed 21 | What properties - if any - need to be changed 22 | 23 | .PARAMETER Server 24 | The server the test was performed against 25 | 26 | .PARAMETER Configuration 27 | The configuration object containing the desired state. 28 | 29 | .PARAMETER ADObject 30 | The AD Object(s) containing the actual state. 31 | 32 | .EXAMPLE 33 | PS C:\> New-TestResult -ObjectType User -Type Changed -Identity $resolvedDN -Changed Description -Server $Server -Configuration $userDefinition -ADObject $adObject 34 | 35 | Creates a new test result object using the specified information. 36 | #> 37 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 38 | [CmdletBinding()] 39 | param ( 40 | [Parameter(Mandatory = $true)] 41 | [string] 42 | $ObjectType, 43 | 44 | [Parameter(Mandatory = $true)] 45 | [string] 46 | $Type, 47 | 48 | [Parameter(Mandatory = $true)] 49 | [string] 50 | $Identity, 51 | 52 | [object[]] 53 | $Changed, 54 | 55 | [Parameter(Mandatory = $true)] 56 | [AllowNull()] 57 | [PSFComputer] 58 | $Server, 59 | 60 | $Configuration, 61 | 62 | $ADObject 63 | ) 64 | 65 | process 66 | { 67 | $object = [PSCustomObject]@{ 68 | PSTypeName = "DCManagement.$ObjectType.TestResult" 69 | Type = $Type 70 | ObjectType = $ObjectType 71 | Identity = $Identity 72 | Changed = $Changed 73 | Server = $Server 74 | Configuration = $Configuration 75 | ADObject = $ADObject 76 | } 77 | Add-Member -InputObject $object -MemberType ScriptMethod -Name ToString -Value { $this.Identity } -Force 78 | $object 79 | } 80 | } -------------------------------------------------------------------------------- /DCManagement/internal/functions/Resolve-ParameterValue.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-ParameterValue 2 | { 3 | <# 4 | .SYNOPSIS 5 | Resolves parameter values, defaulting to configured values. 6 | 7 | .DESCRIPTION 8 | Resolves parameter values, defaulting to configured values. 9 | 10 | .PARAMETER InputObject 11 | The object passed by the user. 12 | 13 | .PARAMETER FullName 14 | The name of the configuration. 15 | 16 | .EXAMPLE 17 | PS C:\> Resolve-ParameterValue -FullName 'DCManagement.Defaults.NoDNS' -InputObject $NoDNS 18 | 19 | Resolves the configuration for NoDNS: 20 | - If it was specified by the user, use $NoDNS variable value 21 | - If it was not, use the 'DCManagement.Defaults.NoDNS' configuration setting 22 | #> 23 | [CmdletBinding()] 24 | param ( 25 | [Parameter(Mandatory = $true)] 26 | [AllowNull()] 27 | [PSObject] 28 | $InputObject, 29 | 30 | [Parameter(Mandatory = $true)] 31 | [string] 32 | $FullName 33 | ) 34 | 35 | process 36 | { 37 | if ($null -ne $InputObject -and '' -ne $InputObject -and $InputObject -isnot [switch]) 38 | { 39 | return $InputObject 40 | } 41 | if ($InputObject -is [switch] -and $InputObject.IsPresent) { return $InputObject } 42 | Get-PSFConfigValue -FullName $FullName 43 | } 44 | } -------------------------------------------------------------------------------- /DCManagement/internal/functions/Revoke-ShareAccess.ps1: -------------------------------------------------------------------------------- 1 | function Revoke-ShareAccess 2 | { 3 | <# 4 | .SYNOPSIS 5 | Removes a specific share permission from the specified share. 6 | 7 | .DESCRIPTION 8 | Removes a specific share permission from the specified share. 9 | Requires user SID and permission match. 10 | This command uses PowerShell remoting to access the target computer. 11 | 12 | .PARAMETER ComputerName 13 | The name of the server to operate against. 14 | 15 | .PARAMETER Credential 16 | The credentials to use for authentication. 17 | 18 | .PARAMETER Name 19 | The name of the share to modifiy. 20 | 21 | .PARAMETER Identity 22 | The SID of the user to revoke permissions for. 23 | 24 | .PARAMETER AccessRight 25 | The rights of the user that has permissions revoked. 26 | 27 | .EXAMPLE 28 | PS C:\> Revoke-ShareAccess @parameters -Name Legal -Identity S-1-5-21-584015949-955715703-1113067636-1105 -AccessRight Full 29 | 30 | Revokes the specified user's full access right to the share "Legal" 31 | #> 32 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWMICmdlet", "")] 33 | [CmdletBinding()] 34 | Param ( 35 | [PSFComputer] 36 | $ComputerName, 37 | 38 | [PSCredential] 39 | $Credential, 40 | 41 | [string] 42 | $Name, 43 | 44 | [string] 45 | $Identity, 46 | 47 | [ValidateSet('Full', 'Change', 'Read')] 48 | [string] 49 | $AccessRight 50 | ) 51 | 52 | begin 53 | { 54 | #region Permission Revocation Scriptblock 55 | $scriptblock = { 56 | param ( 57 | [Hashtable] 58 | $Data 59 | ) 60 | 61 | function Write-Result 62 | { 63 | [CmdletBinding()] 64 | param ( 65 | [switch] 66 | $Failed, 67 | 68 | [string] 69 | $State, 70 | 71 | [string] 72 | $Message 73 | ) 74 | 75 | [pscustomobject]@{ 76 | Success = (-not $Failed) 77 | State = $State 78 | Message = $Message 79 | } 80 | } 81 | 82 | $accessHash = @{ 83 | Full = 2032127 84 | Change = 1245631 85 | Read = 1179817 86 | } 87 | 88 | try { $securitySettings = Get-WmiObject -Query ('SELECT * FROM Win32_LogicalShareSecuritySetting WHERE Name = "{0}"' -f $Data.Name) -ErrorAction Stop } 89 | catch { return Write-Result -Failed -State WMIAccess -Message $_ } 90 | 91 | $securityDescriptor = $securitySettings.GetSecurityDescriptor().Descriptor 92 | 93 | $securityDescriptor.DACL = [System.Management.ManagementBaseObject[]]($securityDescriptor.DACL | Where-Object { 94 | -not ( 95 | $_.Trustee.SIDString -eq $Data.Identity -and 96 | $_.AccessMask -eq $accessHash[$Data.AccessRight] 97 | ) 98 | }) 99 | $result = $securitySettings.SetSecurityDescriptor($securityDescriptor) 100 | 101 | if ($result.ReturnValue -ne 0) { Write-Result -Failed -State 'FailedApply' -Message "Failed to apply with WMI code $($result.ReturnValue)" } 102 | else { Write-Result -State Success -Message 'Permissions successfully revoked' } 103 | } 104 | #endregion Permission Revocation Scriptblock 105 | 106 | $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include ComputerName, Credential 107 | } 108 | process 109 | { 110 | $data = $PSBoundParameters | ConvertTo-PSFHashtable -Include Name, Identity, AccessRight 111 | try { $results = Invoke-PSFCommand @parameters -ScriptBlock $scriptblock -ErrorAction Stop -ArgumentList $data } 112 | catch { Stop-PSFFunction -String 'Revoke-ShareAccess.WinRM.Failed' -StringValues $Identity, $Name, $ComputerName -EnableException $true -ErrorRecord $_ -Target $ComputerName -Cmdlet $PSCmdlet } 113 | 114 | if (-not $results.Success) 115 | { 116 | Stop-PSFFunction -String 'Revoke-ShareAccess.Execution.Failed' -StringValues $Identity, $Name, $ComputerName, $results.Status, $results.Message -EnableException $true -ErrorRecord $_ -Target $ComputerName -Cmdlet $PSCmdlet 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /DCManagement/internal/functions/readme.md: -------------------------------------------------------------------------------- 1 | # Functions 2 | 3 | This is the folder where the internal functions go. 4 | 5 | Depending on the complexity of the module, it is recommended to subdivide them into subfolders. 6 | 7 | The module will pick up all .ps1 files recursively -------------------------------------------------------------------------------- /DCManagement/internal/scriptblocks/scriptblocks.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Stored scriptblocks are available in [PsfValidateScript()] attributes. 3 | This makes it easier to centrally provide the same scriptblock multiple times, 4 | without having to maintain it in separate locations. 5 | 6 | It also prevents lengthy validation scriptblocks from making your parameter block 7 | hard to read. 8 | 9 | Set-PSFScriptblock -Name 'DCManagement.ScriptBlockName' -Scriptblock { 10 | 11 | } 12 | #> -------------------------------------------------------------------------------- /DCManagement/internal/scripts/initialize.ps1: -------------------------------------------------------------------------------- 1 | $PSDefaultParameterValues['Resolve-String:ModuleName'] = 'ADMF.Core' 2 | $PSDefaultParameterValues['Register-StringMapping:ModuleName'] = 'ADMF.Core' 3 | $PSDefaultParameterValues['Clear-StringMapping:ModuleName'] = 'ADMF.Core' 4 | $PSDefaultParameterValues['Unregister-StringMapping:ModuleName'] = 'ADMF.Core' 5 | 6 | Register-PSFCallback -Name DCManagement.ConfigurationReset -ModuleName ADMF.Core -CommandName Clear-AdcConfiguration -ScriptBlock { 7 | Clear-DCConfiguration 8 | } -------------------------------------------------------------------------------- /DCManagement/internal/scripts/license.ps1: -------------------------------------------------------------------------------- 1 | New-PSFLicense -Product 'DCManagement' -Manufacturer 'Friedrich Weinmann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2019-11-14") -Text @" 2 | Copyright (c) 2019 Friedrich Weinmann 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | "@ -------------------------------------------------------------------------------- /DCManagement/internal/scripts/postimport.ps1: -------------------------------------------------------------------------------- 1 | # Add all things you want to run after importing the main code 2 | 3 | # Load Configurations 4 | foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\configurations\*.ps1" -ErrorAction Ignore)) { 5 | . Import-ModuleFile -Path $file.FullName 6 | } 7 | 8 | # Load Scriptblocks 9 | foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\scriptblocks\*.ps1" -ErrorAction Ignore)) { 10 | . Import-ModuleFile -Path $file.FullName 11 | } 12 | 13 | # Load Tab Expansion 14 | foreach ($file in (Get-ChildItem "$($script:ModuleRoot)\internal\tepp\*.tepp.ps1" -ErrorAction Ignore)) { 15 | . Import-ModuleFile -Path $file.FullName 16 | } 17 | 18 | # Load Tab Expansion Assignment 19 | . Import-ModuleFile -Path "$($script:ModuleRoot)\internal\tepp\assignment.ps1" 20 | 21 | # Load License 22 | . Import-ModuleFile -Path "$($script:ModuleRoot)\internal\scripts\license.ps1" 23 | 24 | # Load general initialization code 25 | . Import-ModuleFile -Path "$($script:ModuleRoot)\internal\scripts\initialize.ps1" 26 | 27 | # Load Variables 28 | . Import-ModuleFile -Path "$($script:ModuleRoot)\internal\scripts\variables.ps1" -------------------------------------------------------------------------------- /DCManagement/internal/scripts/preimport.ps1: -------------------------------------------------------------------------------- 1 | # Add all things you want to run before importing the main code 2 | 3 | # Load the strings used in messages 4 | . Import-ModuleFile -Path "$($script:ModuleRoot)\internal\scripts\strings.ps1" -------------------------------------------------------------------------------- /DCManagement/internal/scripts/strings.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This file loads the strings documents from the respective language folders. 3 | This allows localizing messages and errors. 4 | Load psd1 language files for each language you wish to support. 5 | Partial translations are acceptable - when missing a current language message, 6 | it will fallback to English or another available language. 7 | #> 8 | Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'DCManagement' -Language 'en-US' -------------------------------------------------------------------------------- /DCManagement/internal/scripts/variables.ps1: -------------------------------------------------------------------------------- 1 | # Stores Share configuration 2 | $script:shares = @{ } 3 | 4 | # Stores File System Access Rule configuration 5 | $script:fileSystemAccessRules = @{ } -------------------------------------------------------------------------------- /DCManagement/internal/tepp/assignment.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | # Example: 3 | Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name DCManagement.alcohol 4 | #> -------------------------------------------------------------------------------- /DCManagement/internal/tepp/example.tepp.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | # Example: 3 | Register-PSFTeppScriptblock -Name "DCManagement.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' } 4 | #> -------------------------------------------------------------------------------- /DCManagement/internal/tepp/readme.md: -------------------------------------------------------------------------------- 1 | # Tab Expansion 2 | 3 | ## Description 4 | 5 | Modern Tab Expansion was opened to users with the module `Tab Expansion Plus Plus` (TEPP). 6 | 7 | It allows you to define, what options a user is offered when tabbing through input options. This can save a lot of time for the user and is considered a key element in user experience. 8 | 9 | The `PSFramework` offers a simplified way of offering just this, as the two example files show. 10 | 11 | ## Concept 12 | 13 | Custom tab completion is defined in two steps: 14 | 15 | - Define a scriptblock that is run when the user hits `TAB` and provides the strings that are his options. 16 | - Assign that scriptblock to the parameter of a command. You can assign the same scriptblock multiple times. 17 | 18 | ## Structure 19 | 20 | Import order matters. In order to make things work with the default scaffold, follow those rules: 21 | 22 | - All scriptfiles _defining_ completion scriptblocks like this: `*.tepp.ps1` 23 | - Put all your completion assignments in `assignment.ps1` -------------------------------------------------------------------------------- /DCManagement/pending/registrysetting/Get-DCRegistrySetting.ps1: -------------------------------------------------------------------------------- 1 | function Get-DCRegistrySetting 2 | { 3 | [CmdletBinding()] 4 | Param ( 5 | 6 | ) 7 | 8 | begin 9 | { 10 | 11 | } 12 | process 13 | { 14 | 15 | } 16 | end 17 | { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DCManagement/pending/registrysetting/Invoke-DCRegistrySetting.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DCRegistrySetting 2 | { 3 | [CmdletBinding()] 4 | Param ( 5 | 6 | ) 7 | 8 | begin 9 | { 10 | 11 | } 12 | process 13 | { 14 | 15 | } 16 | end 17 | { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DCManagement/pending/registrysetting/Register-DCRegistrySetting.ps1: -------------------------------------------------------------------------------- 1 | function Register-DCRegistrySetting 2 | { 3 | [CmdletBinding()] 4 | Param ( 5 | 6 | ) 7 | 8 | begin 9 | { 10 | 11 | } 12 | process 13 | { 14 | 15 | } 16 | end 17 | { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DCManagement/pending/registrysetting/Test-DCRegistrySetting.ps1: -------------------------------------------------------------------------------- 1 | function Test-DCRegistrySetting 2 | { 3 | [CmdletBinding()] 4 | Param ( 5 | 6 | ) 7 | 8 | begin 9 | { 10 | 11 | } 12 | process 13 | { 14 | 15 | } 16 | end 17 | { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DCManagement/pending/registrysetting/Unregister-DCRegistrySetting.ps1: -------------------------------------------------------------------------------- 1 | function Unregister-DCRegistrySetting 2 | { 3 | [CmdletBinding()] 4 | Param ( 5 | 6 | ) 7 | 8 | begin 9 | { 10 | 11 | } 12 | process 13 | { 14 | 15 | } 16 | end 17 | { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DCManagement/readme.md: -------------------------------------------------------------------------------- 1 | # PSFModule guidance 2 | 3 | This is a finished module layout optimized for implementing the PSFramework. 4 | 5 | If you don't care to deal with the details, this is what you need to do to get started seeing results: 6 | 7 | - Add the functions you want to publish to `/functions/` 8 | - Update the `FunctionsToExport` node in the module manifest (DCManagement.psd1). All functions you want to publish should be in a list. 9 | - Add internal helper functions the user should not see to `/internal/functions/` 10 | 11 | ## Path Warning 12 | 13 | > If you want your module to be compatible with Linux and MacOS, keep in mind that those OS are case sensitive for paths and files. 14 | 15 | `Import-ModuleFile` is preconfigured to resolve the path of the files specified, so it will reliably convert weird path notations the system can't handle. 16 | Content imported through that command thus need not mind the path separator. 17 | If you want to make sure your code too will survive OS-specific path notations, get used to using `Resolve-path` or the more powerful `Resolve-PSFPath`. -------------------------------------------------------------------------------- /DCManagement/snippets/help_par_ContextName.snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | help_par_ContextName 5 | 6 | Infernal Associates 7 | 8 | Expansion 9 | 10 |
11 | 12 | 13 | 17 | 18 | 19 |
20 |
-------------------------------------------------------------------------------- /DCManagement/snippets/help_par_Server_Credential.snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | help_par_Server_Credential 5 | 6 | Infernal Associates 7 | 8 | Expansion 9 | 10 |
11 | 12 | 13 | 18 | 19 | 20 |
21 |
-------------------------------------------------------------------------------- /DCManagement/tests/functions/readme.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This is where the function tests go. 4 | 5 | Make sure to put them in folders reflecting the actual module structure. 6 | 7 | It is not necessary to differentiate between internal and public functions here. -------------------------------------------------------------------------------- /DCManagement/tests/general/FileIntegrity.Exceptions.ps1: -------------------------------------------------------------------------------- 1 | # List of forbidden commands 2 | $global:BannedCommands = @( 3 | 'Write-Host', 4 | 'Write-Verbose', 5 | 'Write-Warning', 6 | 'Write-Error', 7 | 'Write-Output', 8 | 'Write-Information', 9 | 'Write-Debug', 10 | 11 | # Use CIM instead where possible 12 | 'Get-WmiObject', 13 | 'Invoke-WmiMethod', 14 | 'Register-WmiEvent', 15 | 'Remove-WmiObject', 16 | 'Set-WmiInstance' 17 | ) 18 | 19 | <# 20 | Contains list of exceptions for banned cmdlets. 21 | Insert the file names of files that may contain them. 22 | 23 | Example: 24 | "Write-Host" = @('Write-PSFHostColor.ps1','Write-PSFMessage.ps1') 25 | #> 26 | $global:MayContainCommand = @{ 27 | "Write-Host" = @() 28 | "Write-Verbose" = @() 29 | "Write-Warning" = @() 30 | "Write-Error" = @() 31 | "Write-Output" = @() 32 | "Write-Information" = @() 33 | "Write-Debug" = @() 34 | 'Get-WmiObject' = @('Grant-ShareAccess.ps1', 'Revoke-ShareAccess.ps1') 35 | } -------------------------------------------------------------------------------- /DCManagement/tests/general/FileIntegrity.Tests.ps1: -------------------------------------------------------------------------------- 1 | $moduleRoot = (Resolve-Path "$global:testroot\..").Path 2 | 3 | . "$global:testroot\general\FileIntegrity.Exceptions.ps1" 4 | 5 | Describe "Verifying integrity of module files" { 6 | BeforeAll { 7 | function Get-FileEncoding 8 | { 9 | <# 10 | .SYNOPSIS 11 | Tests a file for encoding. 12 | 13 | .DESCRIPTION 14 | Tests a file for encoding. 15 | 16 | .PARAMETER Path 17 | The file to test 18 | #> 19 | [CmdletBinding()] 20 | Param ( 21 | [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] 22 | [Alias('FullName')] 23 | [string] 24 | $Path 25 | ) 26 | 27 | if ($PSVersionTable.PSVersion.Major -lt 6) 28 | { 29 | [byte[]]$byte = get-content -Encoding byte -ReadCount 4 -TotalCount 4 -Path $Path 30 | } 31 | else 32 | { 33 | [byte[]]$byte = Get-Content -AsByteStream -ReadCount 4 -TotalCount 4 -Path $Path 34 | } 35 | 36 | if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) { 'UTF8 BOM' } 37 | elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) { 'Unicode' } 38 | elseif ($byte[0] -eq 0 -and $byte[1] -eq 0 -and $byte[2] -eq 0xfe -and $byte[3] -eq 0xff) { 'UTF32' } 39 | elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) { 'UTF7' } 40 | else { 'Unknown' } 41 | } 42 | } 43 | 44 | Context "Validating PS1 Script files" { 45 | $allFiles = Get-ChildItem -Path $moduleRoot -Recurse | Where-Object Name -like "*.ps1" | Where-Object FullName -NotLike "$moduleRoot\tests\*" 46 | 47 | foreach ($file in $allFiles) 48 | { 49 | $name = $file.FullName.Replace("$moduleRoot\", '') 50 | 51 | It "[$name] Should have UTF8 encoding with Byte Order Mark" -TestCases @{ file = $file } { 52 | Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM' 53 | } 54 | 55 | It "[$name] Should have no trailing space" -TestCases @{ file = $file } { 56 | ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0}).LineNumber | Should -BeNullOrEmpty 57 | } 58 | 59 | $tokens = $null 60 | $parseErrors = $null 61 | $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$parseErrors) 62 | 63 | It "[$name] Should have no syntax errors" -TestCases @{ parseErrors = $parseErrors } { 64 | $parseErrors | Should -BeNullOrEmpty 65 | } 66 | 67 | foreach ($command in $global:BannedCommands) 68 | { 69 | if ($global:MayContainCommand["$command"] -notcontains $file.Name) 70 | { 71 | It "[$name] Should not use $command" -TestCases @{ tokens = $tokens; command = $command } { 72 | $tokens | Where-Object Text -EQ $command | Should -BeNullOrEmpty 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | Context "Validating help.txt help files" { 80 | $allFiles = Get-ChildItem -Path $moduleRoot -Recurse | Where-Object Name -like "*.help.txt" | Where-Object FullName -NotLike "$moduleRoot\tests\*" 81 | 82 | foreach ($file in $allFiles) 83 | { 84 | $name = $file.FullName.Replace("$moduleRoot\", '') 85 | 86 | It "[$name] Should have UTF8 encoding" -TestCases @{ file = $file } { 87 | Get-FileEncoding -Path $file.FullName | Should -Be 'UTF8 BOM' 88 | } 89 | 90 | It "[$name] Should have no trailing space" -TestCases @{ file = $file } { 91 | ($file | Select-String "\s$" | Where-Object { $_.Line.Trim().Length -gt 0 } | Measure-Object).Count | Should -Be 0 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /DCManagement/tests/general/Help.Exceptions.ps1: -------------------------------------------------------------------------------- 1 | # List of functions that should be ignored 2 | $global:FunctionHelpTestExceptions = @( 3 | 4 | ) 5 | 6 | <# 7 | List of arrayed enumerations. These need to be treated differently. Add full name. 8 | Example: 9 | 10 | "Sqlcollaborative.Dbatools.Connection.ManagementConnectionType[]" 11 | #> 12 | $global:HelpTestEnumeratedArrays = @( 13 | 14 | ) 15 | 16 | <# 17 | Some types on parameters just fail their validation no matter what. 18 | For those it becomes possible to skip them, by adding them to this hashtable. 19 | Add by following this convention: = @() 20 | Example: 21 | 22 | "Get-DbaCmObject" = @("DoNotUse") 23 | #> 24 | $global:HelpTestSkipParameterType = @{ 25 | 26 | } 27 | -------------------------------------------------------------------------------- /DCManagement/tests/general/Help.Tests.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .NOTES 3 | The original test this is based upon was written by June Blender. 4 | After several rounds of modifications it stands now as it is, but the honor remains hers. 5 | 6 | Thank you June, for all you have done! 7 | 8 | .DESCRIPTION 9 | This test evaluates the help for all commands in a module. 10 | 11 | .PARAMETER SkipTest 12 | Disables this test. 13 | 14 | .PARAMETER CommandPath 15 | List of paths under which the script files are stored. 16 | This test assumes that all functions have their own file that is named after themselves. 17 | These paths are used to search for commands that should exist and be tested. 18 | Will search recursively and accepts wildcards, make sure only functions are found 19 | 20 | .PARAMETER ModuleName 21 | Name of the module to be tested. 22 | The module must already be imported 23 | 24 | .PARAMETER ExceptionsFile 25 | File in which exceptions and adjustments are configured. 26 | In it there should be two arrays and a hashtable defined: 27 | $global:FunctionHelpTestExceptions 28 | $global:HelpTestEnumeratedArrays 29 | $global:HelpTestSkipParameterType 30 | These can be used to tweak the tests slightly in cases of need. 31 | See the example file for explanations on each of these usage and effect. 32 | #> 33 | [CmdletBinding()] 34 | Param ( 35 | [switch] 36 | $SkipTest, 37 | 38 | [string[]] 39 | $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions"), 40 | 41 | [string] 42 | $ModuleName = "DCManagement", 43 | 44 | [string] 45 | $ExceptionsFile = "$global:testroot\general\Help.Exceptions.ps1" 46 | ) 47 | if ($SkipTest) { return } 48 | . $ExceptionsFile 49 | 50 | $includedNames = (Get-ChildItem $CommandPath -Recurse -File | Where-Object Name -like "*.ps1").BaseName 51 | $commands = Get-Command -Module (Get-Module $ModuleName) -CommandType Cmdlet, Function, Workflow | Where-Object Name -in $includedNames 52 | 53 | ## When testing help, remember that help is cached at the beginning of each session. 54 | ## To test, restart session. 55 | 56 | 57 | foreach ($command in $commands) { 58 | $commandName = $command.Name 59 | 60 | # Skip all functions that are on the exclusions list 61 | if ($global:FunctionHelpTestExceptions -contains $commandName) { continue } 62 | 63 | # The module-qualified command fails on Microsoft.PowerShell.Archive cmdlets 64 | $Help = Get-Help $commandName -ErrorAction SilentlyContinue 65 | 66 | Describe "Test help for $commandName" { 67 | 68 | # If help is not found, synopsis in auto-generated help is the syntax diagram 69 | It "should not be auto-generated" -TestCases @{ Help = $Help } { 70 | $Help.Synopsis | Should -Not -BeLike '*`[``]*' 71 | } 72 | 73 | # Should be a description for every function 74 | It "gets description for $commandName" -TestCases @{ Help = $Help } { 75 | $Help.Description | Should -Not -BeNullOrEmpty 76 | } 77 | 78 | # Should be at least one example 79 | It "gets example code from $commandName" -TestCases @{ Help = $Help } { 80 | ($Help.Examples.Example | Select-Object -First 1).Code | Should -Not -BeNullOrEmpty 81 | } 82 | 83 | # Should be at least one example description 84 | It "gets example help from $commandName" -TestCases @{ Help = $Help } { 85 | ($Help.Examples.Example.Remarks | Select-Object -First 1).Text | Should -Not -BeNullOrEmpty 86 | } 87 | 88 | Context "Test parameter help for $commandName" { 89 | 90 | $common = 'Debug', 'ErrorAction', 'ErrorVariable', 'InformationAction', 'InformationVariable', 'OutBuffer', 'OutVariable', 'PipelineVariable', 'Verbose', 'WarningAction', 'WarningVariable' 91 | 92 | $parameters = $command.ParameterSets.Parameters | Sort-Object -Property Name -Unique | Where-Object Name -notin $common 93 | $parameterNames = $parameters.Name 94 | $HelpParameterNames = $Help.Parameters.Parameter.Name | Sort-Object -Unique 95 | foreach ($parameter in $parameters) { 96 | $parameterName = $parameter.Name 97 | $parameterHelp = $Help.parameters.parameter | Where-Object Name -EQ $parameterName 98 | 99 | # Should be a description for every parameter 100 | It "gets help for parameter: $parameterName : in $commandName" -TestCases @{ parameterHelp = $parameterHelp } { 101 | $parameterHelp.Description.Text | Should -Not -BeNullOrEmpty 102 | } 103 | 104 | $codeMandatory = $parameter.IsMandatory.toString() 105 | It "help for $parameterName parameter in $commandName has correct Mandatory value" -TestCases @{ parameterHelp = $parameterHelp; codeMandatory = $codeMandatory } { 106 | $parameterHelp.Required | Should -Be $codeMandatory 107 | } 108 | 109 | if ($HelpTestSkipParameterType[$commandName] -contains $parameterName) { continue } 110 | 111 | $codeType = $parameter.ParameterType.Name 112 | 113 | if ($parameter.ParameterType.IsEnum) { 114 | # Enumerations often have issues with the typename not being reliably available 115 | $names = $parameter.ParameterType::GetNames($parameter.ParameterType) 116 | # Parameter type in Help should match code 117 | It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { 118 | $parameterHelp.parameterValueGroup.parameterValue | Should -be $names 119 | } 120 | } 121 | elseif ($parameter.ParameterType.FullName -in $HelpTestEnumeratedArrays) { 122 | # Enumerations often have issues with the typename not being reliably available 123 | $names = [Enum]::GetNames($parameter.ParameterType.DeclaredMembers[0].ReturnType) 124 | It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ parameterHelp = $parameterHelp; names = $names } { 125 | $parameterHelp.parameterValueGroup.parameterValue | Should -be $names 126 | } 127 | } 128 | else { 129 | # To avoid calling Trim method on a null object. 130 | $helpType = if ($parameterHelp.parameterValue) { $parameterHelp.parameterValue.Trim() } 131 | # Parameter type in Help should match code 132 | It "help for $commandName has correct parameter type for $parameterName" -TestCases @{ helpType = $helpType; codeType = $codeType } { 133 | $helpType | Should -be $codeType 134 | } 135 | } 136 | } 137 | foreach ($helpParm in $HelpParameterNames) { 138 | # Shouldn't find extra parameters in help. 139 | It "finds help parameter in code: $helpParm" -TestCases @{ helpParm = $helpParm; parameterNames = $parameterNames } { 140 | $helpParm -in $parameterNames | Should -Be $true 141 | } 142 | } 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /DCManagement/tests/general/Manifest.Tests.ps1: -------------------------------------------------------------------------------- 1 | Describe "Validating the module manifest" { 2 | $moduleRoot = (Resolve-Path "$global:testroot\..").Path 3 | $manifest = ((Get-Content "$moduleRoot\DCManagement.psd1") -join "`n") | Invoke-Expression 4 | Context "Basic resources validation" { 5 | $files = Get-ChildItem "$moduleRoot\functions" -Recurse -File | Where-Object Name -like "*.ps1" 6 | It "Exports all functions in the public folder" -TestCases @{ files = $files; manifest = $manifest } { 7 | 8 | $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '<=').InputObject 9 | $functions | Should -BeNullOrEmpty 10 | } 11 | It "Exports no function that isn't also present in the public folder" -TestCases @{ files = $files; manifest = $manifest } { 12 | $functions = (Compare-Object -ReferenceObject $files.BaseName -DifferenceObject $manifest.FunctionsToExport | Where-Object SideIndicator -Like '=>').InputObject 13 | $functions | Should -BeNullOrEmpty 14 | } 15 | 16 | It "Exports none of its internal functions" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } { 17 | $files = Get-ChildItem "$moduleRoot\internal\functions" -Recurse -File -Filter "*.ps1" 18 | $files | Where-Object BaseName -In $manifest.FunctionsToExport | Should -BeNullOrEmpty 19 | } 20 | } 21 | 22 | Context "Individual file validation" { 23 | It "The root module file exists" -TestCases @{ moduleRoot = $moduleRoot; manifest = $manifest } { 24 | Test-Path "$moduleRoot\$($manifest.RootModule)" | Should -Be $true 25 | } 26 | 27 | foreach ($format in $manifest.FormatsToProcess) 28 | { 29 | It "The file $format should exist" -TestCases @{ moduleRoot = $moduleRoot; format = $format } { 30 | Test-Path "$moduleRoot\$format" | Should -Be $true 31 | } 32 | } 33 | 34 | foreach ($type in $manifest.TypesToProcess) 35 | { 36 | It "The file $type should exist" -TestCases @{ moduleRoot = $moduleRoot; type = $type } { 37 | Test-Path "$moduleRoot\$type" | Should -Be $true 38 | } 39 | } 40 | 41 | foreach ($assembly in $manifest.RequiredAssemblies) 42 | { 43 | if ($assembly -like "*.dll") { 44 | It "The file $assembly should exist" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } { 45 | Test-Path "$moduleRoot\$assembly" | Should -Be $true 46 | } 47 | } 48 | else { 49 | It "The file $assembly should load from the GAC" -TestCases @{ moduleRoot = $moduleRoot; assembly = $assembly } { 50 | { Add-Type -AssemblyName $assembly } | Should -Not -Throw 51 | } 52 | } 53 | } 54 | 55 | foreach ($tag in $manifest.PrivateData.PSData.Tags) 56 | { 57 | It "Tags should have no spaces in name" -TestCases @{ tag = $tag } { 58 | $tag -match " " | Should -Be $false 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /DCManagement/tests/general/PSScriptAnalyzer.Tests.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param ( 3 | [switch] 4 | $SkipTest, 5 | 6 | [string[]] 7 | $CommandPath = @("$global:testroot\..\functions", "$global:testroot\..\internal\functions") 8 | ) 9 | 10 | if ($SkipTest) { return } 11 | 12 | $global:__pester_data.ScriptAnalyzer = New-Object System.Collections.ArrayList 13 | 14 | Describe 'Invoking PSScriptAnalyzer against commandbase' { 15 | $commandFiles = foreach ($path in $CommandPath) { Get-ChildItem -Path $path -Recurse | Where-Object Name -like "*.ps1" } 16 | $scriptAnalyzerRules = Get-ScriptAnalyzerRule 17 | 18 | foreach ($file in $commandFiles) 19 | { 20 | Context "Analyzing $($file.BaseName)" { 21 | $analysis = Invoke-ScriptAnalyzer -Path $file.FullName -ExcludeRule PSAvoidTrailingWhitespace, PSShouldProcess 22 | 23 | forEach ($rule in $scriptAnalyzerRules) 24 | { 25 | It "Should pass $rule" -TestCases @{ analysis = $analysis; rule = $rule } { 26 | If ($analysis.RuleName -contains $rule) 27 | { 28 | $analysis | Where-Object RuleName -EQ $rule -outvariable failures | ForEach-Object { $null = $global:__pester_data.ScriptAnalyzer.Add($_) } 29 | 30 | 1 | Should -Be 0 31 | } 32 | else 33 | { 34 | 0 | Should -Be 0 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /DCManagement/tests/general/strings.Exceptions.ps1: -------------------------------------------------------------------------------- 1 | $exceptions = @{ } 2 | 3 | <# 4 | A list of entries that MAY be in the language files, without causing the tests to fail. 5 | This is commonly used in modules that generate localized messages straight from C#. 6 | Specify the full key as it is written in the language files, do not prepend the modulename, 7 | as you would have to in C# code. 8 | 9 | Example: 10 | $exceptions['LegalSurplus'] = @( 11 | 'Exception.Streams.FailedCreate' 12 | 'Exception.Streams.FailedDispose' 13 | ) 14 | #> 15 | $exceptions['LegalSurplus'] = @( 16 | 17 | ) 18 | 19 | $exceptions -------------------------------------------------------------------------------- /DCManagement/tests/general/strings.Tests.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This test verifies, that all strings that have been used, 4 | are listed in the language files and thus have a message being displayed. 5 | 6 | It also checks, whether the language files have orphaned entries that need cleaning up. 7 | #> 8 | 9 | 10 | 11 | Describe "Testing localization strings" { 12 | $moduleRoot = (Get-Module DCManagement).ModuleBase 13 | $stringsResults = Export-PSMDString -ModuleRoot $moduleRoot 14 | $exceptions = & "$global:testroot\general\strings.Exceptions.ps1" 15 | 16 | foreach ($stringEntry in $stringsResults) { 17 | if ($stringEntry.String -eq "key") { continue } # Skipping the template default entry 18 | It "Should be used & have text: $($stringEntry.String)" -TestCases @{ stringEntry = $stringEntry } { 19 | if ($exceptions.LegalSurplus -notcontains $stringEntry.String) { 20 | $stringEntry.Surplus | Should -BeFalse 21 | } 22 | $stringEntry.Text | Should -Not -BeNullOrEmpty 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /DCManagement/tests/pester.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | $TestGeneral = $true, 3 | 4 | $TestFunctions = $true, 5 | 6 | [ValidateSet('None', 'Normal', 'Detailed', 'Diagnostic')] 7 | [Alias('Show')] 8 | $Output = "None", 9 | 10 | $Include = "*", 11 | 12 | $Exclude = "" 13 | ) 14 | 15 | Write-PSFMessage -Level Important -Message "Starting Tests" 16 | 17 | Write-PSFMessage -Level Important -Message "Importing Module" 18 | 19 | $global:testroot = $PSScriptRoot 20 | $global:__pester_data = @{ } 21 | 22 | Remove-Module DCManagement -ErrorAction Ignore 23 | Import-Module "$PSScriptRoot\..\DCManagement.psd1" 24 | Import-Module "$PSScriptRoot\..\DCManagement.psm1" -Force 25 | 26 | # Need to import explicitly so we can use the configuration class 27 | Import-Module Pester 28 | 29 | Write-PSFMessage -Level Important -Message "Creating test result folder" 30 | $null = New-Item -Path "$PSScriptRoot\..\.." -Name TestResults -ItemType Directory -Force 31 | 32 | $totalFailed = 0 33 | $totalRun = 0 34 | 35 | $testresults = @() 36 | $config = [PesterConfiguration]::Default 37 | $config.TestResult.Enabled = $true 38 | 39 | #region Run General Tests 40 | if ($TestGeneral) 41 | { 42 | Write-PSFMessage -Level Important -Message "Modules imported, proceeding with general tests" 43 | foreach ($file in (Get-ChildItem "$PSScriptRoot\general" | Where-Object Name -like "*.Tests.ps1")) 44 | { 45 | if ($file.Name -notlike $Include) { continue } 46 | if ($file.Name -like $Exclude) { continue } 47 | 48 | Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" 49 | $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" 50 | $config.Run.Path = $file.FullName 51 | $config.Run.PassThru = $true 52 | $config.Output.Verbosity = $Output 53 | $results = Invoke-Pester -Configuration $config 54 | foreach ($result in $results) 55 | { 56 | $totalRun += $result.TotalCount 57 | $totalFailed += $result.FailedCount 58 | $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { 59 | $testresults += [pscustomobject]@{ 60 | Block = $_.Block 61 | Name = "It $($_.Name)" 62 | Result = $_.Result 63 | Message = $_.ErrorRecord.DisplayErrorMessage 64 | } 65 | } 66 | } 67 | } 68 | } 69 | #endregion Run General Tests 70 | 71 | $global:__pester_data.ScriptAnalyzer | Out-Host 72 | 73 | #region Test Commands 74 | if ($TestFunctions) 75 | { 76 | Write-PSFMessage -Level Important -Message "Proceeding with individual tests" 77 | foreach ($file in (Get-ChildItem "$PSScriptRoot\functions" -Recurse -File | Where-Object Name -like "*Tests.ps1")) 78 | { 79 | if ($file.Name -notlike $Include) { continue } 80 | if ($file.Name -like $Exclude) { continue } 81 | 82 | Write-PSFMessage -Level Significant -Message " Executing $($file.Name)" 83 | $config.TestResult.OutputPath = Join-Path "$PSScriptRoot\..\..\TestResults" "TEST-$($file.BaseName).xml" 84 | $config.Run.Path = $file.FullName 85 | $config.Run.PassThru = $true 86 | $config.Output.Verbosity = $Output 87 | $results = Invoke-Pester -Configuration $config 88 | foreach ($result in $results) 89 | { 90 | $totalRun += $result.TotalCount 91 | $totalFailed += $result.FailedCount 92 | $result.Tests | Where-Object Result -ne 'Passed' | ForEach-Object { 93 | $testresults += [pscustomobject]@{ 94 | Block = $_.Block 95 | Name = "It $($_.Name)" 96 | Result = $_.Result 97 | Message = $_.ErrorRecord.DisplayErrorMessage 98 | } 99 | } 100 | } 101 | } 102 | } 103 | #endregion Test Commands 104 | 105 | $testresults | Sort-Object Describe, Context, Name, Result, Message | Format-List 106 | 107 | if ($totalFailed -eq 0) { Write-PSFMessage -Level Critical -Message "All $totalRun tests executed without a single failure!" } 108 | else { Write-PSFMessage -Level Critical -Message "$totalFailed tests out of $totalRun tests failed!" } 109 | 110 | if ($totalFailed -gt 0) 111 | { 112 | throw "$totalFailed / $totalRun tests failed!" 113 | } -------------------------------------------------------------------------------- /DCManagement/tests/readme.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | This is the folder, where all the tests go. 4 | 5 | Those are subdivided in two categories: 6 | 7 | - General 8 | - Function 9 | 10 | ## General Tests 11 | 12 | General tests are function generic and test for general policies. 13 | 14 | These test scan answer questions such as: 15 | 16 | - Is my module following my style guides? 17 | - Does any of my scripts have a syntax error? 18 | - Do my scripts use commands I do not want them to use? 19 | - Do my commands follow best practices? 20 | - Do my commands have proper help? 21 | 22 | Basically, these allow a general module health check. 23 | 24 | These tests are already provided as part of the template. 25 | 26 | ## Function Tests 27 | 28 | A healthy module should provide unit and integration tests for the commands & components it ships. 29 | Only then can be guaranteed, that they will actually perform as promised. 30 | 31 | However, as each such test must be specific to the function it tests, there cannot be much in the way of templates. -------------------------------------------------------------------------------- /DCManagement/xml/DCManagement.Format.ps1xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | DCManagement.TestResults 6 | 7 | DCManagement.FSAccessRule.TestResult 8 | DCManagement.Share.TestResult 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | DCManagement.TestResult 17 | 18 | DCManagement.TestResults 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Server 34 | 35 | 36 | Type 37 | 38 | 39 | ObjectType 40 | 41 | 42 | Identity 43 | 44 | 45 | Changed 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /DCManagement/xml/DCManagement.Types.ps1xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Deserialized.Foo.Bar 6 | 7 | 8 | PSStandardMembers 9 | 10 | 11 | 12 | TargetTypeForDeserialization 13 | 14 | 15 | Foo.Bar 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Foo.Bar 24 | 25 | 26 | SerializationData 27 | 28 | PSFramework.Serialization.SerializationTypeConverter 29 | GetSerializationData 30 | 31 | 32 | 33 | 34 | PSFramework.Serialization.SerializationTypeConverter 35 | 36 | 37 | -------------------------------------------------------------------------------- /DCManagement/xml/readme.md: -------------------------------------------------------------------------------- 1 | # XML 2 | 3 | This is the folder where project XML files go, notably: 4 | 5 | - Format XML 6 | - Type Extension XML 7 | 8 | External help files should _not_ be placed in this folder! 9 | 10 | ## Notes on Files and Naming 11 | 12 | There should be only one format file and one type extension file per project, as importing them has a notable impact on import times. 13 | 14 | - The Format XML should be named `DCManagement.Format.ps1xml` 15 | - The Type Extension XML should be named `DCManagement.Types.ps1xml` 16 | 17 | ## Tools 18 | 19 | ### New-PSMDFormatTableDefinition 20 | 21 | This function will take an input object and generate format xml for an auto-sized table. 22 | 23 | It provides a simple way to get started with formats. 24 | 25 | ### Get-PSFTypeSerializationData 26 | 27 | ``` 28 | C# Warning! 29 | This section is only interest if you're using C# together with PowerShell. 30 | ``` 31 | 32 | This function generates type extension XML that allows PowerShell to convert types written in C# to be written to file and restored from it without being 'Deserialized'. Also works for jobs or remoting, if both sides have the `PSFramework` module and type extension loaded. 33 | 34 | In order for a class to be eligible for this, it needs to conform to the following rules: 35 | 36 | - Have the `[Serializable]` attribute 37 | - Be public 38 | - Have an empty constructor 39 | - Allow all public properties/fields to be set (even if setting it doesn't do anything) without throwing an exception. 40 | 41 | ``` 42 | non-public properties and fields will be lost in this process! 43 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Friedrich Weinmann 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DCManagement 2 | 3 | ## Synopsis 4 | 5 | Module to manage Domain Controller deployments and configuration 6 | 7 | For full documentation, see [the project website](https://admf.one) 8 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | pool: 2 | name: Hosted VS2017 3 | steps: 4 | - task: PowerShell@2 5 | displayName: Prerequisites 6 | inputs: 7 | targetType: filePath 8 | filePath: './build/vsts-prerequisites.ps1' 9 | 10 | - task: PowerShell@2 11 | displayName: Validate 12 | inputs: 13 | targetType: filePath 14 | filePath: './build/vsts-validate.ps1' 15 | 16 | - task: PublishTestResults@2 17 | displayName: 'Publish Test Results **/TEST-*.xml' 18 | inputs: 19 | testResultsFormat: NUnit 20 | condition: always() 21 | -------------------------------------------------------------------------------- /build/AzureFunction.readme.md: -------------------------------------------------------------------------------- 1 | # Setting up the release pipeline: 2 | 3 | ## Preliminary 4 | 5 | Setting up a release pipeline, set the trigger to do continuous integration against the master branch only. 6 | In Stage 1 set up a tasksequence: 7 | 8 | ## 1) PowerShell Task: Prerequisites 9 | 10 | Have it execute `vsts-prerequisites.ps1` 11 | 12 | ## 2) PowerShell Task: Validate 13 | 14 | Have it execute `vsts-prerequisites.ps1` 15 | 16 | ## 3) PowerShell Task: Build 17 | 18 | Have it execute `vsts-build.ps1`. 19 | The task requires two parameters: 20 | 21 | - `-LocalRepo` 22 | - `-WorkingDirectory $(System.DefaultWorkingDirectory)/_�name�` 23 | 24 | ## 4) Publish Test Results 25 | 26 | Configure task to pick up nunit type of tests (rather than the default junit). 27 | Configure task to execute, even if previous steps failed or the task sequence was cancelled. 28 | 29 | ## 5) PowerShell Task: Package Function 30 | 31 | Have it execute `vsts-packageFunction.ps1` 32 | 33 | ## 6) Azure Function AppDeploy 34 | 35 | Configure to publish to the correct function app. -------------------------------------------------------------------------------- /build/filesAfter.txt: -------------------------------------------------------------------------------- 1 | # List all files that are loaded in the postimport.ps1 2 | # In the order they are loaded during postimport 3 | 4 | internal\configurations\*.ps1 5 | internal\scriptblocks\*.ps1 6 | internal\tepp\*.tepp.ps1 7 | internal\tepp\assignment.ps1 8 | internal\scripts\license.ps1 9 | internal\scripts\initialize.ps1 10 | internal\scripts\variables.ps1 -------------------------------------------------------------------------------- /build/filesBefore.txt: -------------------------------------------------------------------------------- 1 | # List all files that are loaded in the preimport.ps1 2 | # In the order they are loaded during preimport 3 | 4 | internal\scripts\strings.ps1 -------------------------------------------------------------------------------- /build/vsts-build.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | This script publishes the module to the gallery. 3 | It expects as input an ApiKey authorized to publish the module. 4 | 5 | Insert any build steps you may need to take before publishing it here. 6 | #> 7 | param ( 8 | $ApiKey, 9 | 10 | $WorkingDirectory, 11 | 12 | $Repository = 'PSGallery', 13 | 14 | [switch] 15 | $LocalRepo, 16 | 17 | [switch] 18 | $SkipPublish, 19 | 20 | [switch] 21 | $AutoVersion 22 | ) 23 | 24 | #region Handle Working Directory Defaults 25 | if (-not $WorkingDirectory) 26 | { 27 | if ($env:RELEASE_PRIMARYARTIFACTSOURCEALIAS) 28 | { 29 | $WorkingDirectory = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath $env:RELEASE_PRIMARYARTIFACTSOURCEALIAS 30 | } 31 | else { $WorkingDirectory = $env:SYSTEM_DEFAULTWORKINGDIRECTORY } 32 | } 33 | if (-not $WorkingDirectory) { $WorkingDirectory = Split-Path $PSScriptRoot } 34 | #endregion Handle Working Directory Defaults 35 | 36 | # Prepare publish folder 37 | Write-PSFMessage -Level Important -Message "Creating and populating publishing directory" 38 | $publishDir = New-Item -Path $WorkingDirectory -Name publish -ItemType Directory -Force 39 | Copy-Item -Path "$($WorkingDirectory)\DCManagement" -Destination $publishDir.FullName -Recurse -Force 40 | 41 | #region Gather text data to compile 42 | $text = @() 43 | $processed = @() 44 | 45 | # Gather Stuff to run before 46 | foreach ($line in (Get-Content "$($PSScriptRoot)\filesBefore.txt" | Where-Object { $_ -notlike "#*" })) 47 | { 48 | if ([string]::IsNullOrWhiteSpace($line)) { continue } 49 | 50 | $basePath = Join-Path "$($publishDir.FullName)\DCManagement" $line 51 | foreach ($entry in (Resolve-PSFPath -Path $basePath)) 52 | { 53 | $item = Get-Item $entry 54 | if ($item.PSIsContainer) { continue } 55 | if ($item.FullName -in $processed) { continue } 56 | $text += [System.IO.File]::ReadAllText($item.FullName) 57 | $processed += $item.FullName 58 | } 59 | } 60 | 61 | # Gather commands 62 | Get-ChildItem -Path "$($publishDir.FullName)\DCManagement\internal\functions\" -Recurse -File -Filter "*.ps1" | ForEach-Object { 63 | $text += [System.IO.File]::ReadAllText($_.FullName) 64 | } 65 | Get-ChildItem -Path "$($publishDir.FullName)\DCManagement\functions\" -Recurse -File -Filter "*.ps1" | ForEach-Object { 66 | $text += [System.IO.File]::ReadAllText($_.FullName) 67 | } 68 | 69 | # Gather stuff to run afterwards 70 | foreach ($line in (Get-Content "$($PSScriptRoot)\filesAfter.txt" | Where-Object { $_ -notlike "#*" })) 71 | { 72 | if ([string]::IsNullOrWhiteSpace($line)) { continue } 73 | 74 | $basePath = Join-Path "$($publishDir.FullName)\DCManagement" $line 75 | foreach ($entry in (Resolve-PSFPath -Path $basePath)) 76 | { 77 | $item = Get-Item $entry 78 | if ($item.PSIsContainer) { continue } 79 | if ($item.FullName -in $processed) { continue } 80 | $text += [System.IO.File]::ReadAllText($item.FullName) 81 | $processed += $item.FullName 82 | } 83 | } 84 | #endregion Gather text data to compile 85 | 86 | #region Update the psm1 file 87 | $fileData = Get-Content -Path "$($publishDir.FullName)\DCManagement\DCManagement.psm1" -Raw 88 | $fileData = $fileData.Replace('""', '""') 89 | $fileData = $fileData.Replace('""', ($text -join "`n`n")) 90 | [System.IO.File]::WriteAllText("$($publishDir.FullName)\DCManagement\DCManagement.psm1", $fileData, [System.Text.Encoding]::UTF8) 91 | #endregion Update the psm1 file 92 | 93 | #region Updating the Module Version 94 | if ($AutoVersion) 95 | { 96 | Write-PSFMessage -Level Important -Message "Updating module version numbers." 97 | try { [version]$remoteVersion = (Find-Module 'DCManagement' -Repository $Repository -ErrorAction Stop).Version } 98 | catch 99 | { 100 | Stop-PSFFunction -Message "Failed to access $($Repository)" -EnableException $true -ErrorRecord $_ 101 | } 102 | if (-not $remoteVersion) 103 | { 104 | Stop-PSFFunction -Message "Couldn't find DCManagement on repository $($Repository)" -EnableException $true 105 | } 106 | $newBuildNumber = $remoteVersion.Build + 1 107 | [version]$localVersion = (Import-PowerShellDataFile -Path "$($publishDir.FullName)\DCManagement\DCManagement.psd1").ModuleVersion 108 | Update-ModuleManifest -Path "$($publishDir.FullName)\DCManagement\DCManagement.psd1" -ModuleVersion "$($localVersion.Major).$($localVersion.Minor).$($newBuildNumber)" 109 | } 110 | #endregion Updating the Module Version 111 | 112 | #region Publish 113 | if ($SkipPublish) { return } 114 | if ($LocalRepo) 115 | { 116 | # Dependencies must go first 117 | Write-PSFMessage -Level Important -Message "Creating Nuget Package for module: PSFramework" 118 | New-PSMDModuleNugetPackage -ModulePath (Get-Module -Name PSFramework).ModuleBase -PackagePath . 119 | Write-PSFMessage -Level Important -Message "Creating Nuget Package for module: DCManagement" 120 | New-PSMDModuleNugetPackage -ModulePath "$($publishDir.FullName)\DCManagement" -PackagePath . 121 | } 122 | else 123 | { 124 | # Publish to Gallery 125 | Write-PSFMessage -Level Important -Message "Publishing the DCManagement module to $($Repository)" 126 | Publish-Module -Path "$($publishDir.FullName)\DCManagement" -NuGetApiKey $ApiKey -Force -Repository $Repository 127 | } 128 | #endregion Publish -------------------------------------------------------------------------------- /build/vsts-createFunctionClientModule.ps1: -------------------------------------------------------------------------------- 1 |  2 | <# 3 | .SYNOPSIS 4 | Build script that generates a client module for REST API endpoints of a Azure PowerShell Functions project. 5 | 6 | .DESCRIPTION 7 | Build script that generates a client module for REST API endpoints of a Azure PowerShell Functions project. 8 | 9 | .PARAMETER ApiKey 10 | The API key to use to publish the module to a Nuget Repository 11 | 12 | .PARAMETER WorkingDirectory 13 | The root folder from which to build the module. 14 | 15 | .PARAMETER Repository 16 | The name of the repository to publish to. 17 | Defaults to PSGallery. 18 | 19 | .PARAMETER LocalRepo 20 | Instead of publishing to a gallery, drop a nuget package in the root folder. 21 | This package can then be picked up in a later step for publishing to Azure Artifacts. 22 | 23 | .PARAMETER ModuleName 24 | The name to give to the client module. 25 | By default, the client module will be named '.Client'. 26 | 27 | .PARAMETER IncludeFormat 28 | Include the format xml of the source module for the client module. 29 | 30 | .PARAMETER IncludeType 31 | Include the type extension xml of the source module for the client module. 32 | 33 | .PARAMETER IncludeAssembly 34 | Include the binaries of the source module for the client module. 35 | #> 36 | param ( 37 | $ApiKey, 38 | 39 | $WorkingDirectory, 40 | 41 | $Repository = 'PSGallery', 42 | 43 | [switch] 44 | $LocalRepo, 45 | 46 | $ModuleName, 47 | 48 | [switch] 49 | $IncludeFormat, 50 | 51 | [switch] 52 | $IncludeType, 53 | 54 | [switch] 55 | $IncludeAssembly 56 | ) 57 | 58 | #region Handle Working Directory Defaults 59 | if (-not $WorkingDirectory) 60 | { 61 | if ($env:RELEASE_PRIMARYARTIFACTSOURCEALIAS) 62 | { 63 | $WorkingDirectory = Join-Path -Path $env:SYSTEM_DEFAULTWORKINGDIRECTORY -ChildPath $env:RELEASE_PRIMARYARTIFACTSOURCEALIAS 64 | } 65 | else { $WorkingDirectory = $env:SYSTEM_DEFAULTWORKINGDIRECTORY } 66 | } 67 | #endregion Handle Working Directory Defaults 68 | 69 | Write-PSFMessage -Level Host -Message 'Starting Build: Client Module' 70 | $parentModule = 'DCManagement' 71 | if (-not $ModuleName) { $ModuleName = 'DCManagement.Client' } 72 | Write-PSFMessage -Level Host -Message 'Creating Folder Structure' 73 | $workingRoot = New-Item -Path $WorkingDirectory -Name $ModuleName -ItemType Directory 74 | $publishRoot = Join-Path -Path $WorkingDirectory -ChildPath 'publish\DCManagement' 75 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\clientModule\functions" -Destination "$($workingRoot.FullName)\" -Recurse 76 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\clientModule\internal" -Destination "$($workingRoot.FullName)\" -Recurse 77 | Copy-Item -Path "$($publishRoot)\en-us" -Destination "$($workingRoot.FullName)\" -Recurse 78 | $functionFolder = Get-Item -Path "$($workingRoot.FullName)\functions" 79 | 80 | #region Create Functions 81 | $encoding = [PSFEncoding]'utf8' 82 | $functionsText = Get-Content -Path "$($WorkingDirectory)\azFunctionResources\clientModule\function.ps1" -Raw 83 | 84 | Write-PSFMessage -Level Host -Message 'Creating Functions' 85 | foreach ($functionSourceFile in (Get-ChildItem -Path "$($publishRoot)\functions" -Recurse -Filter '*.ps1')) 86 | { 87 | Write-PSFMessage -Level Host -Message " Processing function: $($functionSourceFile.BaseName)" 88 | $condensedName = $functionSourceFile.BaseName -replace '-', '' 89 | 90 | #region Load Overrides 91 | $override = @{ } 92 | if (Test-Path -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).psd1") 93 | { 94 | $override = Import-PowerShellDataFile -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).psd1" 95 | } 96 | if (Test-Path -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($condensedName).psd1") 97 | { 98 | $override = Import-PowerShellDataFile -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($condensedName).psd1" 99 | } 100 | if ($override.NoClientFunction) 101 | { 102 | Write-PSFMessage -Level Host -Message " Override 'NoClientFunction' detected, skipping!" 103 | continue 104 | } 105 | 106 | # If there is an definition override, use it and continue 107 | if (Test-Path -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).ps1") 108 | { 109 | Write-PSFMessage -Level Host -Message " Override function definition detected, using override" 110 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).ps1" -Destination $functionFolder.FullName 111 | continue 112 | } 113 | 114 | # Figure out the Rest Method to use 115 | $methodName = 'Post' 116 | if ($override.RestMethods) 117 | { 118 | $methodName = $override.RestMethods | Where-Object { $_ -ne 'Get' } | Select-Object -First 1 119 | } 120 | 121 | #endregion Load Overrides 122 | 123 | $currentFunctionsText = $functionsText -replace '%functionname%', $functionSourceFile.BaseName -replace '%condensedname%', $condensedName -replace '%method%', $methodName 124 | 125 | $parsedFunction = Read-PSMDScript -Path $functionSourceFile.FullName 126 | $functionAst = $parsedFunction.Ast.EndBlock.Statements | Where-Object { 127 | $_ -is [System.Management.Automation.Language.FunctionDefinitionAst] 128 | } | Select-Object -First 1 129 | 130 | $end = $functionAst.Body.ParamBlock.Extent.EndOffSet 131 | $start = $functionAst.Body.Extent.StartOffSet + 1 132 | $currentFunctionsText = $currentFunctionsText.Replace('%parameter%', $functionAst.Body.Extent.Text.SubString(1, ($end - $start))) 133 | 134 | Write-PSFMessage -Level Host -Message " Creating file: $($functionFolder.FullName)\$($functionSourceFile.Name)" 135 | [System.IO.File]::WriteAllText("$($functionFolder.FullName)\$($functionSourceFile.Name)", $currentFunctionsText, $encoding) 136 | } 137 | $functionsToExport = (Get-ChildItem -Path $functionFolder.FullName -Recurse -Filter *.ps1).BaseName | Sort-Object 138 | #endregion Create Functions 139 | 140 | #region Create Core Module Files 141 | # Get Manifest of published version, in order to catch build-phase changes such as module version. 142 | $originalManifestData = Import-PowerShellDataFile -Path "$publishRoot\DCManagement.psd1" 143 | $prereqHash = @{ 144 | ModuleName = 'PSFramework' 145 | ModuleVersion = (Get-Module PSFramework).Version 146 | } 147 | $paramNewModuleManifest = @{ 148 | Path = ('{0}\{1}.psd1' -f $workingRoot.FullName, $ModuleName) 149 | FunctionsToExport = $functionsToExport 150 | CompanyName = $originalManifestData.CompanyName 151 | Author = $originalManifestData.Author 152 | Description = $originalManifestData.Description 153 | ModuleVersion = $originalManifestData.ModuleVersion 154 | RootModule = ('{0}.psm1' -f $ModuleName) 155 | Copyright = $originalManifestData.Copyright 156 | TypesToProcess = @() 157 | FormatsToProcess = @() 158 | RequiredAssemblies = @() 159 | RequiredModules = @($prereqHash) 160 | CompatiblePSEditions = 'Core', 'Desktop' 161 | PowerShellVersion = '5.1' 162 | } 163 | 164 | if ($IncludeAssembly) { $paramNewModuleManifest.RequiredAssemblies = $originalManifestData.RequiredAssemblies } 165 | if ($IncludeFormat) { $paramNewModuleManifest.FormatsToProcess = $originalManifestData.FormatsToProcess } 166 | if ($IncludeType) { $paramNewModuleManifest.TypesToProcess = $originalManifestData.TypesToProcess } 167 | Write-PSFMessage -Level Host -Message "Creating Module Manifest for module: $ModuleName" 168 | New-ModuleManifest @paramNewModuleManifest 169 | 170 | Write-PSFMessage -Level Host -Message "Copying additional module files" 171 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\clientModule\moduleroot.psm1" -Destination "$($workingRoot.FullName)\$($ModuleName).psm1" 172 | Copy-Item -Path "$($WorkingDirectory)\LICENSE" -Destination "$($workingRoot.FullName)\" 173 | #endregion Create Core Module Files 174 | 175 | #region Transfer Additional Content 176 | if ($IncludeAssembly) 177 | { 178 | Copy-Item -Path "$publishRoot\bin" -Destination "$($workingRoot.FullName)\" -Recurse 179 | } 180 | if ($IncludeFormat -or $IncludeType) 181 | { 182 | Copy-Item -Path "$publishRoot\xml" -Destination "$($workingRoot.FullName)\" -Recurse 183 | } 184 | #endregion Transfer Additional Content 185 | 186 | #region Publish 187 | if ($LocalRepo) 188 | { 189 | # Dependencies must go first 190 | Write-PSFMessage -Level Important -Message "Creating Nuget Package for module: PSFramework" 191 | New-PSMDModuleNugetPackage -ModulePath (Get-Module -Name PSFramework).ModuleBase -PackagePath . -WarningAction SilentlyContinue 192 | Write-PSFMessage -Level Important -Message "Creating Nuget Package for module: DCManagement" 193 | New-PSMDModuleNugetPackage -ModulePath $workingRoot.FullName -PackagePath . -EnableException 194 | } 195 | else 196 | { 197 | # Publish to Gallery 198 | Write-PSFMessage -Level Important -Message "Publishing the DCManagement module to $($Repository)" 199 | Publish-Module -Path $workingRoot.FullName -NuGetApiKey $ApiKey -Force -Repository $Repository 200 | } 201 | #endregion Publish -------------------------------------------------------------------------------- /build/vsts-packageFunction.ps1: -------------------------------------------------------------------------------- 1 |  2 | <# 3 | .SYNOPSIS 4 | Packages an Azure Functions project, ready to release. 5 | 6 | .DESCRIPTION 7 | Packages an Azure Functions project, ready to release. 8 | Should be part of the release pipeline, after ensuring validation. 9 | 10 | Look into the 'AzureFunctionRest' template for generating functions for the module if you do. 11 | 12 | .PARAMETER WorkingDirectory 13 | The root folder to work from. 14 | 15 | .PARAMETER Repository 16 | The name of the repository to use for gathering dependencies from. 17 | #> 18 | param ( 19 | $WorkingDirectory = "$($env:SYSTEM_DEFAULTWORKINGDIRECTORY)\_DCManagement", 20 | 21 | $Repository = 'PSGallery', 22 | 23 | [switch] 24 | $IncludeAZ 25 | ) 26 | 27 | $moduleName = 'DCManagement' 28 | 29 | # Prepare Paths 30 | Write-PSFMessage -Level Host -Message "Creating working folders" 31 | $moduleRoot = Join-Path -Path $WorkingDirectory -ChildPath 'publish' 32 | $workingRoot = New-Item -Path $WorkingDirectory -Name 'working' -ItemType Directory 33 | $modulesFolder = New-Item -Path $workingRoot.FullName -Name Modules -ItemType Directory 34 | 35 | # Fill out the modules folder 36 | Write-PSFMessage -Level Host -Message "Transfering built module data into working directory" 37 | Copy-Item -Path "$moduleRoot\$moduleName" -Destination $modulesFolder.FullName -Recurse -Force 38 | foreach ($dependency in (Import-PowerShellDataFile -Path "$moduleRoot\$moduleName\$moduleName.psd1").RequiredModules) 39 | { 40 | $param = @{ 41 | Repository = $Repository 42 | Name = $dependency.ModuleName 43 | Path = $modulesFolder.FullName 44 | } 45 | if ($dependency -is [string]) { $param['Name'] = $dependency } 46 | if ($dependency.RequiredVersion) 47 | { 48 | $param['RequiredVersion'] = $dependency.RequiredVersion 49 | } 50 | Write-PSFMessage -Level Host -Message "Preparing Dependency: $($param['Name'])" 51 | Save-Module @param 52 | } 53 | 54 | # Generate function configuration 55 | Write-PSFMessage -Level Host -Message 'Generating function configuration' 56 | $runTemplate = Get-Content -Path "$($WorkingDirectory)\azFunctionResources\run.ps1" -Raw 57 | foreach ($functionSourceFile in (Get-ChildItem -Path "$($moduleRoot)\$moduleName\functions" -Recurse -Filter '*.ps1')) 58 | { 59 | Write-PSFMessage -Level Host -Message " Processing function: $functionSourceFile" 60 | $condensedName = $functionSourceFile.BaseName -replace '-', '' 61 | $functionFolder = New-Item -Path $workingRoot.FullName -Name $condensedName -ItemType Directory 62 | 63 | #region Load Overrides 64 | $override = @{ } 65 | if (Test-Path -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).psd1") 66 | { 67 | $override = Import-PowerShellDataFile -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).psd1" 68 | } 69 | if (Test-Path -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($condensedName).psd1") 70 | { 71 | $override = Import-PowerShellDataFile -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($condensedName).psd1" 72 | } 73 | #endregion Load Overrides 74 | 75 | #region Create Function Configuration 76 | $restMethods = 'get', 'post' 77 | if ($override.RestMethods) { $restMethods = $override.RestMethods } 78 | 79 | Set-Content -Path "$($functionFolder.FullName)\function.json" -Value @" 80 | { 81 | "bindings": [ 82 | { 83 | "authLevel": "function", 84 | "type": "httpTrigger", 85 | "direction": "in", 86 | "name": "Request", 87 | "methods": [ 88 | "$($restMethods -join "`", 89 | `"")" 90 | ] 91 | }, 92 | { 93 | "type": "http", 94 | "direction": "out", 95 | "name": "Response" 96 | } 97 | ], 98 | "disabled": false 99 | } 100 | "@ 101 | #endregion Create Function Configuration 102 | 103 | #region Override Function Configuration 104 | if (Test-Path -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).json") 105 | { 106 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($functionSourceFile.BaseName).json" -Destination "$($functionFolder.FullName)\function.json" -Force 107 | } 108 | if (Test-Path -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($condensedName).json") 109 | { 110 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\functionOverride\$($condensedName).json" -Destination "$($functionFolder.FullName)\function.json" -Force 111 | } 112 | #endregion Override Function Configuration 113 | 114 | # Generate the run.ps1 file 115 | $runText = $runTemplate -replace '%functionname%', $functionSourceFile.BaseName 116 | $runText | Set-Content -Path "$($functionFolder.FullName)\run.ps1" -Encoding UTF8 117 | } 118 | 119 | # Transfer common files 120 | Write-PSFMessage -Level Host -Message "Transfering core function data" 121 | if ($IncludeAZ) 122 | { 123 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\host-az.json" -Destination "$($workingroot.FullName)\host.json" 124 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\requirements.psd1" -Destination "$($workingroot.FullName)\" 125 | } 126 | else 127 | { 128 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\host.json" -Destination "$($workingroot.FullName)\" 129 | } 130 | Copy-Item -Path "$($WorkingDirectory)\azFunctionResources\local.settings.json" -Destination "$($workingroot.FullName)\" 131 | 132 | # Build the profile file 133 | $text = @() 134 | $text += Get-Content -Path "$($WorkingDirectory)\azFunctionResources\profile.ps1" -Raw 135 | foreach ($functionFile in (Get-ChildItem "$($WorkingDirectory)\azFunctionResources\profileFunctions" -Recurse)) 136 | { 137 | $text += Get-Content -Path $functionFile.FullName -Raw 138 | } 139 | $text -join "`n`n" | Set-Content "$($workingroot.FullName)\profile.ps1" 140 | 141 | # Zip It 142 | Write-PSFMessage -Level Host -Message "Creating function archive in '$($WorkingDirectory)\$moduleName.zip'" 143 | Compress-Archive -Path "$($workingroot.FullName)\*" -DestinationPath "$($WorkingDirectory)\$moduleName.zip" -Force -------------------------------------------------------------------------------- /build/vsts-prerequisites.ps1: -------------------------------------------------------------------------------- 1 | $modules = @("Pester", "PSFramework", "PSModuleDevelopment", "PSScriptAnalyzer", 'ResolveString', 'Principal', 'ADMF.Core') 2 | 3 | foreach ($module in $modules) { 4 | Write-Host "Installing $module" -ForegroundColor Cyan 5 | Install-Module $module -Force -SkipPublisherCheck 6 | Import-Module $module -Force -PassThru 7 | } -------------------------------------------------------------------------------- /build/vsts-validate.ps1: -------------------------------------------------------------------------------- 1 | # Guide for available variables and working with secrets: 2 | # https://docs.microsoft.com/en-us/vsts/build-release/concepts/definitions/build/variables?tabs=powershell 3 | 4 | # Needs to ensure things are Done Right and only legal commits to master get built 5 | 6 | # Run internal pester tests 7 | & "$PSScriptRoot\..\DCManagement\tests\pester.ps1" -------------------------------------------------------------------------------- /library/DCManagement/DCManagement.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{C9C8362C-5A58-43BF-AD29-72E4E7525FC6}") = "DCManagement", "DCManagement\DCManagement.csproj", "{65C9ADE8-83EC-40FA-BCB8-5BE31356DD4B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {65C9ADE8-83EC-40FA-BCB8-5BE31356DD4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {65C9ADE8-83EC-40FA-BCB8-5BE31356DD4B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {65C9ADE8-83EC-40FA-BCB8-5BE31356DD4B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {65C9ADE8-83EC-40FA-BCB8-5BE31356DD4B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {4AAB23BA-B23D-40CB-8E3F-E6E1F6A1E60A} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /library/DCManagement/DCManagement/DCManagement.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net4.5.2 5 | 6 | 7 | 8 | ..\..\..\DCManagement\bin 9 | ..\..\..\DCManagement\bin\DCManagement.xml 10 | 11 | 12 | 13 | ..\..\..\DCManagement\bin 14 | ..\..\..\DCManagement\bin\DCManagement.xml 15 | 16 | 17 | 18 | false 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /library/DCManagement/DCManagement/FileSystemPermission.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DCManagement 4 | { 5 | [Flags] 6 | public enum FileSystemPermission : uint 7 | { 8 | ListDirectory = 1, 9 | ReadData = 1, 10 | WriteData = 2, 11 | CreateFiles = 2, 12 | CreateDirectories = 4, 13 | AppendData = 4, 14 | ReadExtendedAttributes = 8, 15 | WriteExtendedAttributes = 16, 16 | Traverse = 32, 17 | ExecuteFile = 32, 18 | DeleteSubdirectoriesAndFiles = 64, 19 | ReadAttributes = 128, 20 | WriteAttributes = 256, 21 | Write = 278, 22 | Delete = 65536, 23 | ReadPermissions = 131072, 24 | Read = 131209, 25 | ReadAndExecute = 131241, 26 | Modify = 197055, 27 | ChangePermissions = 262144, 28 | TakeOwnership = 524288, 29 | Synchronize = 1048576, 30 | FullControl = 2032127, 31 | GenericAll = 268435456, 32 | GenericExecute = 536870912, 33 | GenericWrite = 1073741824, 34 | GenericRead = 2147483648 35 | } 36 | } 37 | --------------------------------------------------------------------------------