diff --git a/DomainManagement/DomainManagement.psd1 b/DomainManagement/DomainManagement.psd1 index cc48eff..911d32f 100644 --- a/DomainManagement/DomainManagement.psd1 +++ b/DomainManagement/DomainManagement.psd1 @@ -139,6 +139,8 @@ 'Unregister-DMOrganizationalUnit' 'Unregister-DMPasswordPolicy' 'Unregister-DMUser' + 'Install-DMJEAEndpoint' + 'Set-DMWinRMMode' ) # Cmdlets to export from this module diff --git a/DomainManagement/en-us/strings.psd1 b/DomainManagement/en-us/strings.psd1 index 2066652..7ea9cca 100644 --- a/DomainManagement/en-us/strings.psd1 +++ b/DomainManagement/en-us/strings.psd1 @@ -2,25 +2,25 @@ # Write-PSFMessage, Stop-PSFFunction or the PSFramework validation scriptblocks @{ 'Assert-ADConnection.Failed' = 'Failed to connect to {0}' # $Server - + 'Assert-Configuration.NotConfigured' = 'No configuration data provided for: {0}' # $Type - - 'Convert-AccessRule.Identity.ResolutionError' = 'Failed to convert identity. This generally means a configuration error, especially when referencing the parent as identity.' # - + + 'Convert-AccessRule.Identity.ResolutionError' = 'Failed to convert identity. This generally means a configuration error, especially when referencing the parent as identity.' # + 'Convert-Principal.Processing' = 'Converting principal: {0}' # $Name 'Convert-Principal.Processing.InputNT' = 'Input detected as NT: {0}' # $Name 'Convert-Principal.Processing.InputSID' = 'Input detected as SID: {0}' # $Name 'Convert-Principal.Processing.NT.LdapFilter' = 'Resolving NT identity via AD using the following filter: {0}' # "(samAccountName=$namePart)" 'Convert-Principal.Processing.NTDetails' = 'Resolved NT identity: Domain = {0} | Name = {1}' # $domainPart, $namePart - + 'General.Invalid.Input' = 'Invalid input: {1}! This command only accepts output objects from {0}' # 'Test-DMAccessRule', $testItem - + 'Get-PermissionGuidMapping.Processing' = 'Processing Permission Guids for domain: {0} (This may take a while)' # $identity - + 'Get-Principal.Resolution.Failed' = 'Failed to resolve principal: SID {0} | Name {1} | ObjectClass {2} | Domain {3}' # $Sid, $Name, $ObjectClass, $Domain - + 'Get-SchemaGuidMapping.Processing' = 'Processing Schema Guids for domain: {0} (This may take a while)' # $identity - + 'Install-GroupPolicy.CopyingFiles' = 'Copying GPO files for "{0}"' # $Configuration.DisplayName 'Install-GroupPolicy.CopyingFiles.Failed' = 'Failed to copy the GPO files for "{0}"' # $Configuration.DisplayName 'Install-GroupPolicy.DeletingImportFiles' = 'Deleting the GPO files for "{0}"' # $Configuration.DisplayName @@ -34,11 +34,11 @@ 'Install-GroupPolicy.ReadingADObject.Failed.Timestamp' = 'Failed to vallidate import of the GPO "{0}". Its last modified timestamp ({1}) is older than the time the import started ({2}). This would usually be the case when the GPO already existed before the process and the import failed for some reason. Please troubleshoot the GPO creation process and the target environment.' # $Configuration.DisplayName, $policyObject.Modified, $timestamp 'Install-GroupPolicy.UpdatingConfigurationFile' = 'Updating the configuration metadata file for "{0}" on the target domain.' # $Configuration.DisplayName 'Install-GroupPolicy.UpdatingConfigurationFile.Failed' = 'Failed to update the configuration metadata file for "{0}" on the target domain. This will cause the GPO to not register as managed and will be flagged as to update on the next test. Please validate filesystem write access to the targeted GPO.' # $Configuration.DisplayName - + 'Invoke-Callback.Invoking' = 'Executing callback: {0}' # $callback.Name 'Invoke-Callback.Invoking.Failed' = 'Error executing callback: {0}' # $callback.Name 'Invoke-Callback.Invoking.Success' = 'Successfully executed callback: {0}' # $callback.Name - + 'Invoke-DMAccessRule.Access.Failed' = 'Failed to access ACL on {0}' # $testItem.Identity 'Invoke-DMAccessRule.AccessRule.Create' = 'Adding access rule for {0}, granting {1} ({2})' # $changeEntry.Configuration.IdentityReference, $changeEntry.Configuration.ActiveDirectoryRights, $changeEntry.Configuration.AccessControlType 'Invoke-DMAccessRule.AccessRule.Creation.Failed' = 'Failed to create accessrule at {0} for {1}' # $testItem.Identity, $changeEntry.Configuration.IdentityReference @@ -47,21 +47,21 @@ 'Invoke-DMAccessRule.ADObject.Missing' = 'Cannot process access rules, due to missing AD object: {0}. Please ensure the domain object is created before trying to apply rules to it!' # $testItem.Identity 'Invoke-DMAccessRule.Processing.Execute' = 'Applying {0} out of {1} intended access rule changes' # ($testItem.Changed.Count - $failedCount), $testItem.Changed.Count 'Invoke-DMAccessRule.Processing.Rules' = 'Processing {1} access rule changes on {0}' # $testItem.Identity, $testItem.Changed.Count - + 'Invoke-DMAcl.MissingADObject' = 'The target object could not be found: {0}' # $testItem.Identity 'Invoke-DMAcl.NoAccess' = 'Failed to access Acl on {0}' # $testItem.Identity 'Invoke-DMAcl.OwnerNotResolved' = 'Was unable to resolve the current owner ({1}) of {0}' # $testItem.Identity, $testItem.ADObject.GetOwner([System.Security.Principal.SecurityIdentifier]) 'Invoke-DMAcl.ShouldManage' = 'The ADObject {0} has no defined ACL state and should either be configured or removed' # $testItem.Identity 'Invoke-DMAcl.UpdatingInheritance' = 'Updating inheritance - Inheritance Disabled: {0}' # $testItem.Configuration.NoInheritance 'Invoke-DMAcl.UpdatingOwner' = 'Granting ownership to {0}' # ($testItem.Configuration.Owner | Resolve-String) - + 'Invoke-DMDomainData.Invocation.Error' = 'An exception was thrown while executing the domain data script "{0}".' # $Name 'Invoke-DMDomainData.Invocation.Error.Terminate' = 'Critical Error: An exception was thrown while executing the domain data script "{0}".' # $Name 'Invoke-DMDomainData.Script.NotFound' = 'Could not find a registered DomainData set with the name "{0}". Be sure to register an appropriate configuration and check for typos.' # $Name 'Invoke-DMDomainData.Script.NotFound.Error' = 'Critical error: Could not find a registered DomainData set with the name "{0}". Be sure to register an appropriate configuration and check for typos.' # $Name - + 'Invoke-DMDomainLevel.Raise.Level' = 'Raising domain level to {0}' # $testItem.Configuration.Level - + 'Invoke-DMGPLink.Delete.AllDisabled' = 'Removing all ({0}) policy links (all of which are disabled)' # $countActual 'Invoke-DMGPLink.Delete.AllEnabled' = 'Removing all ({0}) policy links (all of which are enabled)' # $countActual 'Invoke-DMGPLink.Delete.SomeDisabled' = 'Removing all ({0}) policy links' # $countActual @@ -73,17 +73,17 @@ 'Invoke-DMGPLink.Update.GpoNotFound' = 'Unable to find Group POlicy Object: {0}' # (Resolve-String -Text $_.PolicyName) 'Invoke-DMGPLink.Update.NewGPLinkString' = 'Finished gPLink string being applied to {0}: {1}' # $ADObject.DistinguishedName, $gpLinkString 'Invoke-DMGPLink.Update.SomeDisabled' = 'Updating GPLink - {0} links configured, {1} links present, {2} links present that are not in configuration' # $countConfigured, $countActual, $countNotInConfig - + 'Invoke-DMGPPermission.AD.Access.Error' = 'Error accessing Active Directory for {0} ({1})' # $testResult, $testResult.ADObject.DistinguishedName 'Invoke-DMGPPermission.AD.UpdatingPermission' = 'Updating {0} permission changes on the AD object' # $testResult.Changed.Count 'Invoke-DMGPPermission.Gpo.SyncingPermission' = 'Making {0} permission changes consistent through the GPO Api' # $testResult.Changed.Count 'Invoke-DMGPPermission.Invalid.Input' = 'The input object was not recognized as a valid test result for Group Policy Permissions: {0}' # $testResult 'Invoke-DMGPPermission.Result.Access.Error' = 'The test for {0} failed, due to access error during the test phase!' # $testResult.Identity 'Invoke-DMGPPermission.WinRM.Failed' = 'Failed to connect to {0}' # $computerName - - 'Invoke-DMGroup.Group.Create' = 'Creating active directory group' # + + 'Invoke-DMGroup.Group.Create' = 'Creating active directory group' # 'Invoke-DMGroup.Group.Create.OUExistsNot' = 'Cannot create group {1} : Path does not exist: {0}' # $targetOU, $testItem.Identity - 'Invoke-DMGroup.Group.Delete' = 'Deleting active directory group' # + 'Invoke-DMGroup.Group.Delete' = 'Deleting active directory group' # 'Invoke-DMGroup.Group.InvalidScope' = 'Invalid scope defined for {0}: {1} is not a legal group scope.' # $testItem, $targetScope 'Invoke-DMGroup.Group.Move' = 'Moving active directory group to {0}' # $targetOU 'Invoke-DMGroup.Group.MultipleOldGroups' = 'Cannot rename group to {0}: More than one group exists owning one of the previous names. Conflicting groups: {1}. Please investigate and manually resolve.' # $testItem.Identity, ($testItem.ADObject.Name -join ', ') @@ -92,13 +92,13 @@ 'Invoke-DMGroup.Group.Update.Name' = 'Renaming to {0}' # (Resolve-String -Text $testItem.Configuration.Name) 'Invoke-DMGroup.Group.Update.OUExistsNot' = 'Cannot move active directory group {0} - OU does not exist: {1}' # $testItem.Identity, $targetOU 'Invoke-DMGroup.Group.Update.Scope' = 'Updating the group scope of {0} from {1} to {2}' # $testItem, $testItem.ADObject.GroupScope, $targetScope - + 'Invoke-DMGroupMembership.GroupMember.Add' = 'Adding member to {0}' # $testItem.ADObject.Name 'Invoke-DMGroupMembership.GroupMember.Remove' = 'Removing member from {0}' # $testItem.ADObject.Name 'Invoke-DMGroupMembership.GroupMember.RemoveUnidentified' = 'Removing unidentified foreign security principal from {0}' # $testItem.ADObject.Name 'Invoke-DMGroupMembership.Unidentified' = 'Could not identify current group member: {0}' # $testItem.Identity 'Invoke-DMGroupMembership.Unresolved' = 'Could not identify required group member: {0}' # $testItem.Identity - + 'Invoke-DMGroupPolicy.Delete' = 'Deleting group policy object: {0}.' # $testItem.Identity 'Invoke-DMGroupPolicy.Install.OnBadRegistry' = 'Re-applying GPO "{0}" after detecting a registry setting that is not as defined.' # $testItem.Identity 'Invoke-DMGroupPolicy.Install.OnConfigError' = 'Re-applying GPO "{0}" after failing to read its configuration' # $testItem.Identity @@ -109,27 +109,27 @@ 'Invoke-DMGroupPolicy.Remote.WorkingDirectory.Failed' = 'Failed to create working directory on {0}. This is required for importing GPOs' # $computerName 'Invoke-DMGroupPolicy.Skipping.InCriticalState' = 'Critical error validating {0}. The GPO will be skipped, please manually verify the GPO and bring it into a supportable state.' # $testItem.Identity 'Invoke-DMGroupPolicy.WinRM.Failed' = 'Failed to connect to "{0}" via WinRM/PowerShell Remoting.' # $computerName - + 'Invoke-DMObject.Object.Change' = 'Updating the properties {0}' # ($testItem.Changed -join ", ") 'Invoke-DMObject.Object.Create' = 'Creating {0} object in {1}' # $testItem.Configuration.ObjectClass, $testItem.Identity - - 'Invoke-DMOrganizationalUnit.OU.Create' = 'Creating organizational unit' # + + 'Invoke-DMOrganizationalUnit.OU.Create' = 'Creating organizational unit' # 'Invoke-DMOrganizationalUnit.OU.Create.OUExistsNot' = 'Cannot create OU {1}, parent OU does not exist: {0}' # $targetOU, $testItem.Identity - 'Invoke-DMOrganizationalUnit.OU.Delete' = 'Deleting organizational unit' # + 'Invoke-DMOrganizationalUnit.OU.Delete' = 'Deleting organizational unit' # 'Invoke-DMOrganizationalUnit.OU.Delete.HasChildren' = 'Skipping the deletion of {0} - OU has {1} childitem(s) that would also be deleted. Please manually handle these before proceeding.' # $testItem.ADObject.DistinguishedName, ($childObjects | Measure-Object).Count 'Invoke-DMOrganizationalUnit.OU.Delete.NoAction' = 'Skipping the deletion of {0} - OU deletion has been disabled' # $testItem.Identity 'Invoke-DMOrganizationalUnit.OU.MultipleOldOUs' = 'Cannot rename organizational unit to {0}: More than one organizational unit exists owning one of the previous names. Conflicting organizational unit: {1}. Please investigate and manually resolve.' # $testItem.Identity, ($testItem.ADObject.Name -join ', ') 'Invoke-DMOrganizationalUnit.OU.Rename' = 'Renaming organizational unit to {0}' # (Resolve-String -Text $testItem.Configuration.Name) 'Invoke-DMOrganizationalUnit.OU.Update' = 'Updating {0} on organizational unit' # ($changes.Keys -join ", ") - - 'Invoke-DMPasswordPolicy.PSO.Create' = 'Creating new PSO object' # - 'Invoke-DMPasswordPolicy.PSO.Delete' = 'Deleting PSO object' # + + 'Invoke-DMPasswordPolicy.PSO.Create' = 'Creating new PSO object' # + 'Invoke-DMPasswordPolicy.PSO.Delete' = 'Deleting PSO object' # 'Invoke-DMPasswordPolicy.PSO.Update' = 'Updating properties: {0}' # ($changes.Keys -join ", ") 'Invoke-DMPasswordPolicy.PSO.Update.GroupAssignment' = 'Assigning PSO to {0}' # (Resolve-String -Text $testItem.Configuration.SubjectGroup) - - 'Invoke-DMUser.User.Create' = 'Creating active directory user' # + + 'Invoke-DMUser.User.Create' = 'Creating active directory user' # 'Invoke-DMUser.User.Create.OUExistsNot' = 'Cannot create user {1} : Path does not exist: {0}' # $targetOU, $testItem.Identity - 'Invoke-DMUser.User.Delete' = 'Deleting active directory user' # + 'Invoke-DMUser.User.Delete' = 'Deleting active directory user' # 'Invoke-DMUser.User.Move' = 'Moving active directory user to {0}' # $targetOU 'Invoke-DMUser.User.MultipleOldUsers' = 'Cannot rename user to {0}: More than one user exists owning one of the previous names. Conflicting users: {1}. Please investigate and manually resolve.' # $testItem.Identity, ($testItem.ADObject.Name -join ', ') 'Invoke-DMUser.User.Rename' = 'Renaming active directory user to {0}' # (Resolve-String -Text $testItem.Configuration.SamAccountName) @@ -138,57 +138,57 @@ 'Invoke-DMUser.User.Update.Name' = 'Renaming user to {0}' # (Resolve-String -Text $testItem.Configuration.Name) 'Invoke-DMUser.User.Update.OUExistsNot' = 'Cannot move active directory group {0} - user does not exist: {1}' # $testItem.Identity, $targetOU 'Invoke-DMUser.User.Update.PasswordNeverExpires' = 'Changing user password non-expiration to: {0}' # $testItem.Configuration.PasswordNeverExpires - + 'Remove-GroupPolicy.Deleting' = 'Deleting GPO: {0}' # $ADObject.DisplayName 'Remove-GroupPolicy.Deleting.Failed' = 'Failed to delete GPO: {0}' # $ADObject.DisplayName - + 'Resolve-ContentSearchBase.Exclude.NotFound' = 'Failed to find excluded ou/container: {0}' # $item.Name 'Resolve-ContentSearchBase.Include.NotFound' = 'Failed to find included ou/container: {0}' # $item.Name 'Resolve-ContentSearchBase.Searchbase.Found' = 'Resolved searchbase in {2}: {0} | {1}' # $searchBase.SearchScope, $searchBase.SearchBase, $script:domainContext.Fqdn - + 'Resolve-DMAccessRuleMode.PathResolution.Failed' = 'Unable to resolve path: {0}' # $mode.Path - + 'Resolve-Identity.ParentObject.NoSecurityPrincipal' = 'Error processing parent of {0} : {1} of type {2} is no legal security principal and cannot be assigned permissions!' # $ADObject, $parentObject.Name, $parentObject.ObjectClass - + 'Resolve-PolicyRevision.Result.ErrorOnConfigImport' = 'Failed to read configuration for {0}: {1}' # $Policy.DisplayName, $result.Error.Exception.Message 'Resolve-PolicyRevision.Result.PolicyError' = 'Policy object not found in filesystem: {0}. Check existence and permissions!' # $Policy.DisplayName 'Resolve-PolicyRevision.Result.Result.SuccessNotYetManaged' = 'Policy found: {0}. Has not yet been managed, will need to be overwritten.' # $Policy.DisplayName 'Resolve-PolicyRevision.Result.Success' = 'Found GPO: {0}. Last export ID: {1}. Last updated on {2}' # $Policy.DisplayName, $result.ExportID, $result.Timestamp - + 'Set-DMRedForestContext.Connection.Failed' = 'Failed to connect to {0}' # $Server - + 'Test-DMAccessRule.DefaultPermission.Failed' = 'Failed to retrieve default permissions from Schema when connecting to {0}' # $Server 'Test-DMAccessRule.NoAccess' = 'Failed to access {0}' # $resolvedPath - + 'Test-DMAcl.ADObjectNotFound' = 'The target object could not be found: {0}' # $resolvedPath 'Test-DMAcl.NoAccess' = 'Failed to access Acl on {0}' # $resolvedPath - + 'Test-DMGPLink.OUNotFound' = 'Failed to find the configured OU: {0} - Please validate your OU configuration and bring your OU estate into the desired state first!' # $resolvedName - + 'Test-DMGPPermission.Filter.Path.DoesNotExist.SilentlyContinue' = 'The searchbase for the filter condition {0} could not be found. This however was defined as optional and is not an error: {1}' # $Condition.Name, $searchBase 'Test-DMGPPermission.Filter.Path.DoesNotExist.Stop' = 'The searchbase {1} for the filter condition {0} could not be found. A consistent picture of the desired permission configuration is impossible, terminating!' # $searchBase 'Test-DMGPPermission.Filter.Result' = 'Resolved filter condition {0} to GPOs: {1}' # $key, ($filterToGPOMapping[$key].DisplayName -join ', ') 'Test-DMGPPermission.Identity.Resolution.Error' = 'Failed to resolve the identity of an intended privilege-holder on {0}. Cancelling the processing for this GPO, as correct access configuration is not assured.' # $adObject.DisplayName 'Test-DMGPPermission.Validate.MissingFilterConditions' = 'Critical configuration error: Not all referenced Group Policy Permission filter-conditions were defined. Undefined conditions: {0}' # ($missingConditions -join ", ") 'Test-DMGPPermission.WinRM.Failed' = 'Failed to connect to {0}' # $computerName - + 'Test-DMGPRegistrySetting.TestResult' = 'Finished testing GP Registry settings against {0}. Success: {1} | Status: {2}' # $resolvedName, $result.Success, $result.Status 'Test-DMGPRegistrySetting.WinRM.Failed' = 'Failed to connect to {0}' # $parameters.Server - + 'Test-DMGroupMembership.Assignment.Resolve.Connect' = 'Failed to resolve {2} {1} - Could not connect to {0}' # (Resolve-String -Text $assignment.Domain), (Resolve-String -Text $assignment.Name), $assignment.ItemType 'Test-DMGroupMembership.Assignment.Resolve.NotFound' = 'Successfully queried domain "{0}", but the member {1} of type {2} could not be found' # (Resolve-String -Text $assignment.Domain), (Resolve-String -Text $assignment.Name), $assignment.ItemType 'Test-DMGroupMembership.Group.Access.Failed' = 'Failed to access group {0} in target domain, cannot compare its members with the configured state.' # $resolvedGroupName - + 'Test-DMGroupPolicy.ADObjectAccess.Failed' = 'Failed to access GPO active directory object for: {0}' # $managedPolicy.DistinguishedName 'Test-DMGroupPolicy.DomainData.Failed' = 'Failed to retrieve the domain-specific data for the following sources: {0}' # ($domainDataNames -join ",") 'Test-DMGroupPolicy.PolicyRevision.Lookup.Failed' = 'Failed to resolve GPO in AD: {0}' # $managedPolicies.DisplayName 'Test-DMGroupPolicy.WinRM.Failed' = 'Failed to connect to "{0}" via WinRM/PowerShell Remoting.' # $computerName - + 'Test-DMObject.ADObject.Access.Error' = 'Failed to access {0} while retrieving {1}' # $resolvedPath, ($objectDefinition.Attributes.Keys -join ",") 'Test-DMObject.ADObject.Access.Error2' = 'Failed to access {0}' # $resolvedPath - + 'Test-DMPasswordPolicy.SubjectGroup.NotFound' = 'Failed to find the group {0}, which should be granted permission to the Finegrained Password Policy {1}' # $groupName, $resolvedName - + 'Validate.DomainData.Pattern' = 'Invalid input: {0}. A domain data name must only consist of numbers, letters and underscore.' # , 'Validate.GPPermissionFilter' = 'Invalid GP Permission filter: {0}' # , 'Validate.GPPermissionFilter.InvalidIdentifiers' = 'Invalid filter elements (filter names): {1}. Filters can only consist of letters, numbers and underscore. Filter: {0}' # $_, ($invalidIdentifiers -join ', ') @@ -199,4 +199,17 @@ 'Validate.Name.Pattern' = 'Invalid input: {0}. The name tag must start with a "%", end in a "%" and contain only letters, underscore and numbers inbetween.' # , 'Validate.PermissionFilterName' = 'Invalid input: {0}. GP Permission Filter names may only consist of letters, numbers and underscore characters.' # , 'Validate.TypeName.AccessRule.Failed' = 'The input object {0} could not be verified as a "DomainManagement.AccessRule" object.' # , + 'Install-DMJEAEndPoint.NewSession' = 'Create new PSSession if working remotely.' + 'Install-DMJEAEndPoint.NewSession.LocalHost' = 'Running on local host. Will not create WinRM session.' + 'Install-DMJEAEndPoint.CopyModule' = 'Copy JEA module to target.' + 'Install-DMJEAEndPoint.RunScript' = 'Run installation script' + 'Install-DMJEAEndPoint.RunScript.SessionBroken' = 'WinRM Session broken. This is expected in remote installation' + 'Install-DMJEAEndPoint.TestInstallation' = 'Confirm installation is successful' + 'Install-DMJEAEndPoint.Success' = 'Installation succeeded. Use below output to create JSON file or with Set-DMWinRMMode' + + 'Get-DMObjectDefaultPermission.RunningLocally' = 'Running against local host. Will not use WinRM.' + 'Get-DMObjectDefaultPermission.Mode' = 'Effective WinRM Mode: {0}' + 'Get-DMObjectDefaultPermission.JEAConfigurationName' = 'JEA Configuration Name: {0}' + 'Get-DMObjectDefaultPermission.JEAEndpointServer' = 'JEA Endpoint server: {0}' + 'Set-DMWinRMMode.JEAConfiguration.Missing' = 'JEA Configuration Name must be provided if mode is set to JEA' } \ No newline at end of file diff --git a/DomainManagement/functions/names/Register-DMNameMapping.ps1 b/DomainManagement/functions/names/Register-DMNameMapping.ps1 index 7f2031c..ce35287 100644 --- a/DomainManagement/functions/names/Register-DMNameMapping.ps1 +++ b/DomainManagement/functions/names/Register-DMNameMapping.ps1 @@ -6,7 +6,7 @@ .DESCRIPTION Register a new name mapping. - Mapped names are used for stringr replacement when invoking domain configurations. + Mapped names are used for string replacement when invoking domain configurations. .PARAMETER Name The name of the placeholder to register. diff --git a/DomainManagement/functions/other/Get-DMObjectDefaultPermission.ps1 b/DomainManagement/functions/other/Get-DMObjectDefaultPermission.ps1 index ed01d83..18c2cbc 100644 --- a/DomainManagement/functions/other/Get-DMObjectDefaultPermission.ps1 +++ b/DomainManagement/functions/other/Get-DMObjectDefaultPermission.ps1 @@ -3,20 +3,20 @@ <# .SYNOPSIS Gathers the default object permissions in AD. - + .DESCRIPTION Gathers the default object permissions in AD. Uses PowerShell remoting against the SchemaMaster to determine the default permissions, as local identity resolution is not reliable. - + .PARAMETER ObjectClass The object class to look up. - + .PARAMETER Server The server / domain to work with. - + .PARAMETER Credential The credentials to use for this operation. - + .EXAMPLE PS C:\> Get-DMObjectDefaultPermission -ObjectClass user @@ -30,12 +30,12 @@ $ObjectClass, [PSFComputer] - $Server = '', + $Server = 'localhost', [PSCredential] $Credential ) - + begin { if (-not $script:schemaObjectDefaultPermission) { @@ -76,7 +76,7 @@ $acl = [System.DirectoryServices.ActiveDirectorySecurity]::new() $acl.SetSecurityDescriptorSddlForm($class.defaultSecurityDescriptor) foreach ($rule in $commonAce) { $acl.AddAccessRule($rule) } - + <# if ($class.lDAPDisplayName -eq 'organizationalUnit') { $acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule(([System.Security.Principal.NTAccount]'Everyone'), 'DeleteTree, Delete', 'Deny', '00000000-0000-0000-0000-000000000000', 'None', '00000000-0000-0000-0000-000000000000'))) @@ -103,13 +103,44 @@ return $script:schemaObjectDefaultPermission["$Server"].$ObjectClass } - #region Process Gathering logic - if ($Server -ne '') { + #region Prepare parameters + $parameters['ComputerName'] = $parameters.Server $parameters.Remove("Server") + + #endregion Prepare parameters + + #Check if running locally and change to NoWinRM mode. + $_winRMMode = $script:WinRMMode.Mode + if($Server.IsLocalhost) { + $_winRMMode = 'NoWinRM' + Write-PSFMessage -Level Verbose -String 'Get-DMObjectDefaultPermission.RunningLocally' + } + + try { + #TODO get these messages to work at least in verbose. + Write-PSFMessage -Level Verbose -String 'Get-DMObjectDefaultPermission.Mode' -StringValues $_winRMMode + switch ($_winRMMode) { + 'Default' { + $data = Invoke-PSFCommand @parameters -ScriptBlock $gatherScript -ErrorAction Stop + } + 'JEA' { + $parameters['ConfigurationName'] = $script:WinRMMode.JEAConfigurationName + Write-PSFMessage -Level Verbose -String 'Get-DMObjectDefaultPermission.JEAConfigurationName' -StringValues $script:WinRMMode.JEAConfigurationName + if($script:WinRMMode.JEAEndpointServer){ + $_jeaEndpointServer = $script:WinRMMode.JEAEndpointServer | Resolve-String + $parameters['ComputerName'] = $_jeaEndpointServer + } + Write-PSFMessage -Level Verbose -String 'Get-DMObjectDefaultPermission.JEAEndpointServer' -StringValues $_jeaEndpointServer + $data = Invoke-Command @parameters -ScriptBlock { Get-Dmobjectsdefaultpermissions} -ErrorAction Stop + } + 'NoWinRM'{ + $data = $gatherScript.Invoke() + + } + default {throw "WinRMMode is invalid - $_winRMMode"} + } } - - try { $data = Invoke-PSFCommand @parameters -ScriptBlock $gatherScript -ErrorAction Stop } catch { throw } $script:schemaObjectDefaultPermission["$Server"] = @{ } foreach ($datum in $data) { diff --git a/DomainManagement/functions/other/Install-DMJEAEndPoint.ps1 b/DomainManagement/functions/other/Install-DMJEAEndPoint.ps1 new file mode 100644 index 0000000..e3b1a80 --- /dev/null +++ b/DomainManagement/functions/other/Install-DMJEAEndPoint.ps1 @@ -0,0 +1,223 @@ +function Install-DMJEAEndPoint { + <# + .SYNOPSIS + Installs a new JEA EndPoint. + + .DESCRIPTION + Installs a new JEA EndPoint on a target computer. + This is used when checking AD Object default permissions from different + forest with a non-domain admin account. + The EndPoint can be a dedicated computer or a domain controller. + + .PARAMETER ComputerName + Name of remote endpoint computer name. Default is localhost. + Although it is supported to provide a WinRM session, during installation the session is broken + and a new one is created, therefore you need to be using an account with admin privilages on + remote computer or supply proper credential. + + .PARAMETER Credential + Credential to use. Must have local administrator permissions. + + .PARAMETER JEAIdentity + Group/user/gMSA account used in configuration. + + .EXAMPLE + PS C:\> Install-DMJEAEndpoint -JEAIdentity Domain\username + + Installs JEA endpoint on localhost. + + .EXAMPLE + PS C:\> Install-DMJEAEndPoint -ComputerName jeaendpoint.contoso.com -JEAIdentity Domain\username -Credential $creds + + Installs JEA endpoint on remote computer using supplied credentials. + + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] + [CmdletBinding()] + Param ( + [PSFComputer] $ComputerName = (Get-CimInstance -ClassName 'win32_computersystem').DNSHostName+"."+(Get-CimInstance -ClassName 'win32_computersystem').Domain, #FQDN + + [PSCredential] $Credential, + + [Parameter(Mandatory = $true)] + [string] $JEAIdentity + + ) + + begin { + #region: Utility functions + function New-Session { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] + [CmdletBinding()] + param ([PSFComputer] $ComputerName, [PSCredential] $Credential) + + [PSFComputer] $result = $null + # Are we working on local host? + if ($ComputerName.IsLocalhost) { + $result = $ComputerName + Write-PSFMessage -Level Verbose -String 'Install-DMJEAEndPoint.NewSession.LocalHost' + } + else { + if ($ComputerName.Type -ne 'PSSession') { + $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include Credential + $parameters['ComputerName'] = $ComputerName.InputObject + + $result = New-PSSession @parameters -ErrorAction Stop + } + else { $result = $ComputerName } + } + $result + } + function Copy-JEAModule { + [CmdletBinding()] + Param ([PSFComputer] $ComputerName, $SourceFolderPath) + + if ($ComputerName.IsLocalhost) { + #Running on localhost + $modulesRootPath = "$env:ProgramFiles\WindowsPowerShell\Modules\" + Copy-Item -Path $SourceFolderPath -Destination $modulesRootPath -Recurse -Force -ErrorAction Stop + } + else { # Running on remote computer + $modulesRootPath = Invoke-Command -Session $ComputerName.InputObject -ScriptBlock { "$env:ProgramFiles\WindowsPowerShell\Modules\" } + Copy-Item -ToSession $ComputerName.InputObject -Path $SourceFolderPath -Destination $modulesRootPath -Recurse -Force -ErrorAction Stop + } + + } + function Register-JEAEndpoint { + [CmdletBinding()] + Param ([PSFComputer] $ComputerName, $JEAIdentity) + + $installResult = [PSCustomObject]@{ + Success = $true + Error = $null + } + $installResult = Invoke-PSFCommand -ComputerName $ComputerName -ArgumentList $JEAIdentity -scriptblock { + Param( + $JEAIdentity + ) + $result = [PSCustomObject]@{ + Success = $true + Error = $null + } + try { + + $jeaModulePath = "$env:ProgramFiles\WindowsPowerShell\Modules\" + $jeaModuleSessionConfigurationPath = "$jeaModulePath\JEA_DMJEAModule\1.0.0\sessionconfiguration.pssc" + + $SessionConfiguration = Get-Content -Path $jeaModuleSessionConfigurationPath -ErrorAction 'Stop' + $SessionConfiguration -replace '%JEAIdentity%', $JEAIdentity | Set-Content $jeaModuleSessionConfigurationPath -Force -ErrorAction 'Stop' + + Import-Module -Name JEA_DMJEAModule -ErrorAction 'Stop' + Register-JeaEndpoint_JEA_DMJEAModule -ErrorAction 'Stop' -WarningAction SilentlyContinue + + } + catch { + $result.Success = $false + $result.Error = $_ + } + $result + } -ErrorAction SilentlyContinue # We expect null output from here if all goes well and the session is broken + + Start-Sleep -Seconds 3 # Wait for the session to report as broken + + if($installResult.Success -or $ComputerName.InputObject.State -eq "Broken"){ + + if (-Not $ComputerName.IsLocalhost -and $ComputerName.Type -eq 'PSSession') { #Close any open sessions + Write-PSFMessage -Level Verbose -String 'Install-DMJEAEndPoint.RunScript.SessionBroken' + Remove-PSSession -Session $ComputerName.InputObject -ErrorAction Ignore -WhatIf:$false -Confirm:$false + } + } + else {throw $installResult.Error} + } + function Test-Installation { + [CmdletBinding()] + Param ([PSFComputer] $ComputerName) + + $installResult = Invoke-PSFCommand -ComputerName $ComputerName -scriptblock { + $result = [PSCustomObject]@{ + Success = $true + Error = $null + } + try { + if (Get-PSSessionConfiguration -Name 'JEA_DMJEAModule' -ErrorAction Stop) { + # Nothing to do, just leave it result.success as $true + } + + } + catch { + $result.Success = $false + $result.Error = $_ + } + $result + } + if (-Not $installResult.Success) { + throw $installResult.Error + } + $installResult + } + #endregion: Utility functions + $sourceFolderPath = "$script:moduleroot\internal\JEAEndpoint\JEA_DMJEAModule" + } + + process { + $parameters = $PSBoundParameters | ConvertTo-PSFHashtable -Include ComputerName, Credential + + if ($ComputerName.IsLocalhost) {$parameters.ComputerName = $ComputerName} + + Invoke-PSFProtectedCommand -ActionString 'Install-DMJEAEndPoint.NewSession' -ActionStringValues $ComputerName.InputObject -Target $ComputerName.InputObject -ScriptBlock { + + $parameters.ComputerName = New-Session @parameters + + } -EnableException $EnableException -PSCmdlet $PSCmdlet -whatif:$false -Confirm:$false + if (Test-PSFFunctionInterrupt) { return } + + Invoke-PSFProtectedCommand -ActionString 'Install-DMJEAEndPoint.CopyModule' -Target $ComputerName.ComputerName -ScriptBlock { + Copy-JEAModule -ComputerName $parameters.ComputerName -SourceFolderPath $sourceFolderPath + + } -EnableException $EnableException -PSCmdlet $PSCmdlet + if (Test-PSFFunctionInterrupt) { return } + + Invoke-PSFProtectedCommand -ActionString 'Install-DMJEAEndPoint.RunScript' -Target $ComputerName.ComputerName -ScriptBlock { + Register-JEAEndpoint -ComputerName $parameters.ComputerName -JEAIdentity $JEAIdentity + } -EnableException $EnableException -PSCmdlet $PSCmdlet + if (Test-PSFFunctionInterrupt) { return } + + $parameters.ComputerName = $ComputerName.ComputerName + + Invoke-PSFProtectedCommand -ActionString 'Install-DMJEAEndPoint.NewSession' -ActionStringValues $ComputerName.InputObject -Target $ComputerName.InputObject -ScriptBlock { + + $parameters.ComputerName = New-Session @parameters + + } -EnableException $EnableException -PSCmdlet $PSCmdlet -whatif:$false -Confirm:$false -RetryCount 3 -RetryWait 3s + if (Test-PSFFunctionInterrupt) { return } + + Invoke-PSFProtectedCommand -ActionString 'Install-DMJEAEndPoint.TestInstallation' -ActionStringValues $ComputerName.InputObject -Target $ComputerName.InputObject -ScriptBlock { + + $installResult = Test-Installation -ComputerName $parameters.ComputerName + + } -EnableException $EnableException -PSCmdlet $PSCmdlet -whatif:$false -Confirm:$false + if (Test-PSFFunctionInterrupt) { return } + + + if ($installResult.success) { + Write-PSFMessage -Level Verbose -String 'Install-DMJEAEndPoint.Success' + [PSCustomObject]@{ + PSTypeName = 'DomainManagement.WinRM.Mode' + "Mode" = 'JEA' + "JEAConfigurationName" = 'JEA_DMJEAModule' + "JEAEndpointServer" = "$ComputerName" + } + } + else { + throw $installResult.Error + } + + } + + End { + if (-Not $ComputerName.IsLocalhost -and $parameters.ComputerName.Type -eq 'PSSession') { #Close any open sessions + Remove-PSSession -Session $parameters.ComputerName.InputObject -ErrorAction Ignore -WhatIf:$false -Confirm:$false + } + } +} \ No newline at end of file diff --git a/DomainManagement/functions/system/Set-DMWinRMMode.ps1 b/DomainManagement/functions/system/Set-DMWinRMMode.ps1 new file mode 100644 index 0000000..af01d56 --- /dev/null +++ b/DomainManagement/functions/system/Set-DMWinRMMode.ps1 @@ -0,0 +1,67 @@ +function Set-DMWinRMMode { + <# + .SYNOPSIS + Configures the way the module handles WinRM when gathering default object permissions from schema. + + .DESCRIPTION + Configures the way the module handles WinRM when gathering default object permissions from schema. + Depending on the desired domain configuration, dealing with undesired objects may be desirable. + + This module handles the following configurations: + Mode: + - Default: WinRM is used in full session mode. Connects to the DC. This requires domain admin permission. + - JEA: WinRM will use Just-Enough-Administration, this must be configured on the target DC or a different JEA target. + - noWinRM: WinRM is not used. This implies that the command is run directly on the domain controller. + + When specifying JEA Server name, specify the full DN, inserting '%DomainDN%' (without the quotes) for the domain root. + + .PARAMETER Mode + The mode to operate under. + - Default: WinRM is used in full session mode. Connects to the DC. This requires domain admin permission. + - JEA: WinRM will use Just-Enough-Administration, this must be configured on the target DC or a different JEA target. + - noWinRM: WinRM is not used. This implies that the command is run directly on the domain controller. + + .PARAMETER JEAConfigurationName + Required when WinRMMode is set to JEA. + Defines the JEA endpoint name. + This name must be configured on the target server. + + .PARAMETER JEAEndpointServer + When defined, module will connect to this server instead of domain controller. + Specify the full DN, inserting '%DomainDN%' (without the quotes) for the domain root. + + .EXAMPLE + PS C:\> Set-DMWinRMMode -Mode 'Default' + + Enables Default mode and connects to the DC in full session. + + .EXAMPLE + PS C:\> Set-DMWinRMMode -Mode 'JEA' -JEAConfigurationName 'JEA_DMJEAModule' -JEAEndpointServer 'JEAServer.%DomainDN%' + + Enables JEA mode and connects to dedicated JEA endpoint server. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] + [CmdletBinding()] + Param ( + [ValidateSet('Default', 'JEA', 'NoWinRM')] + [string] + $Mode, + + [string] + $JEAConfigurationName, + + [string] + $JEAEndpointServer + ) + begin { + if ($Mode -eq 'JEA' -and -not $JEAConfigurationName){ + Stop-PSFFunction -String 'Set-DMWinRMMode.JEAConfiguration.Missing' -EnableException $true -Cmdlet $PSCmdlet -Category InvalidArgument + } + } + + process { + if ($Mode) { $script:WinRMMode.Mode = $Mode } + if (Test-PSFParameterBinding -ParameterName JEAConfigurationName) { $script:WinRMMode.JEAConfigurationName = $JEAConfigurationName } + if (Test-PSFParameterBinding -ParameterName JEAEndpointServer) { $script:WinRMMode.JEAEndpointServer = $JEAEndpointServer } + } +} \ No newline at end of file diff --git a/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/JEA_DMJEAModule.psd1 b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/JEA_DMJEAModule.psd1 new file mode 100644 index 0000000..10202f7 Binary files /dev/null and b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/JEA_DMJEAModule.psd1 differ diff --git a/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/JEA_DMJEAModule.psm1 b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/JEA_DMJEAModule.psm1 new file mode 100644 index 0000000..0cb09ea --- /dev/null +++ b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/JEA_DMJEAModule.psm1 @@ -0,0 +1,21 @@ +Import-Module -Name ActiveDirectory +$script:ModuleRoot = $PSScriptRoot + +foreach ($scriptFile in (Get-ChildItem -Path "$($script:ModuleRoot)\internal\scriptsPre\" -Recurse)) +{ + . $scriptFile.FullName +} + +foreach ($functionFile in (Get-ChildItem -Path "$($script:ModuleRoot)\internal\functions\" -Recurse)) +{ + . $functionFile.FullName +} +foreach ($functionFile in (Get-ChildItem -Path "$($script:ModuleRoot)\functions\" -Recurse)) +{ + . $functionFile.FullName +} + +foreach ($scriptFile in (Get-ChildItem -Path "$($script:ModuleRoot)\internal\scriptsPost\" -Recurse)) +{ + . $scriptFile.FullName +} \ No newline at end of file diff --git a/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/RoleCapabilities/DMJEARole.psrc b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/RoleCapabilities/DMJEARole.psrc new file mode 100644 index 0000000..d9c0d34 Binary files /dev/null and b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/RoleCapabilities/DMJEARole.psrc differ diff --git a/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/functions/Get-Dmobjectsdefaultpermissions.ps1 b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/functions/Get-Dmobjectsdefaultpermissions.ps1 new file mode 100644 index 0000000..da6442e --- /dev/null +++ b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/functions/Get-Dmobjectsdefaultpermissions.ps1 @@ -0,0 +1,20 @@ +function Get-Dmobjectsdefaultpermissions +{ +$rootDSE = Get-ADRootDSE -Server $env:COMPUTERNAME +$classes = Get-ADObject -Server $env:COMPUTERNAME -SearchBase $rootDSE.schemaNamingContext -LDAPFilter '(objectCategory=classSchema)' -Properties defaultSecurityDescriptor, lDAPDisplayName +foreach ($class in $classes) { + $acl = [System.DirectoryServices.ActiveDirectorySecurity]::new() + $acl.SetSecurityDescriptorSddlForm($class.defaultSecurityDescriptor) + $access = foreach ($accessRule in $acl.Access) { + try { Add-Member -InputObject $accessRule -MemberType NoteProperty -Name SID -Value $accessRule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) } + catch { + # Do nothing, don't want the property if no SID is to be had + } + $accessRule + } + [PSCustomObject]@{ + Class = $class.lDAPDisplayName + Access = $access + } +} +} \ No newline at end of file diff --git a/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/functions/Register-JeaEndpoint_JEA_DMJEAModule.ps1 b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/functions/Register-JeaEndpoint_JEA_DMJEAModule.ps1 new file mode 100644 index 0000000..50ca4a4 --- /dev/null +++ b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/functions/Register-JeaEndpoint_JEA_DMJEAModule.ps1 @@ -0,0 +1,26 @@ +function Register-JeaEndpoint_JEA_DMJEAModule +{ +<# + .SYNOPSIS + Registers the module's JEA session configuration in WinRM. + + .DESCRIPTION + Registers the module's JEA session configuration in WinRM. + This effectively enables the module as a remoting endpoint. + + .EXAMPLE + PS C:\> Register-JeaEndpoint_JEA_DMJEAModule + + Register this module in WinRM as a remoting target. +#> + [CmdletBinding()] + Param ( + + ) + + process + { + $moduleName = (Get-Item -Path "$script:ModuleRoot\*.psd1").BaseName + Register-PSSessionConfiguration -Name $moduleName -Path "$script:ModuleRoot\sessionconfiguration.pssc" -Force + } +} \ No newline at end of file diff --git a/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/sessionconfiguration.pssc b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/sessionconfiguration.pssc new file mode 100644 index 0000000..25e5cb8 Binary files /dev/null and b/DomainManagement/internal/JEAEndpoint/JEA_DMJEAModule/1.0.0/sessionconfiguration.pssc differ diff --git a/DomainManagement/internal/scripts/variables.ps1 b/DomainManagement/internal/scripts/variables.ps1 index 5fe2df9..f59a567 100644 --- a/DomainManagement/internal/scripts/variables.ps1 +++ b/DomainManagement/internal/scripts/variables.ps1 @@ -106,6 +106,14 @@ $script:contentSearchBases = [PSCustomObject]@{ Server = '' } +# WinRM Mode +$script:WinRMMode = [PSCustomObject]@{ + PSTypeName = 'DomainManagement.WinRM.Mode' + Mode = 'Default' # Allowed Values: Default / JEA / NoWinRM + JEAConfigurationName = '' + JEAEndPointServer = '' +} + # Domain Context $script:domainContext = [PSCustomObject]@{ Name = ''