Tonsillectomy rectally: working with AD in Powershell without AD cmdlets

In Windows Server 2008 appeared first on wonderful PowerShell cmdlets for working with active directory. These beautiful, logical, intuitive and extremely powerful tools evoked in me a feeling of sadness, if not to say "frustration": they were inaccessible to me, anyadike non-core offices. All eleven networks which I served was based on Windows 2003 R2.

Eleven unrelated domains into eleven disjoint networks in different cities scattered across the Far East. And none of them — nor the fact that "Seven", even "Vista" no, that puts an end to trying use the AD cmdlets in conjunction with two thousand third.


The problem was formulated as follows — "to create a code, able to perform basic operations for managing AD from PowerShell scripts, executable under Windows XP / 2003". How it was solved, read jabracada (gently, crutches; a lot of text and code).

Context


Lately dull, in General, the work of a few anyadike was complicated by a period of abnormal activity of system administrators and other superiors under the it: they decided to make mass configuration change of the server services, user workstations and other it services. Naturally, the hands of the generalists, since the normal tools like Altiris or MS SCCM they had.

At some point I realized that physically do not have time to implement their brilliant ideas in the entrusted to me networks, and thinking about automation. The analysis of working time shows that, first and foremost, you need to speed up the process of replicating changes to AD. For this purpose, as it seemed to me, was the necessary Active Directory Comandlets (for which it was required to purchase at least one license for Windows 7).

Business executives and system administrators was relentless: "Why do we need a new system if the old works fine? Oh, it works not great? But you have to be so perfect! Why else would you pay so much? Oh, by the way, if you need a new system, buy it yourself with your own money and use! And anyway, if you are not able to solve business problems in the framework of the proposed tools, go out!"

"Okay," I answered: "to go out" very much not wanted. In the end, the implementation of "hotelok" leadership is one of the key differences anyadike from a real system administrator. It became clear that once again will have to "get creative" and "out". As a platform to build a system of "ersatz-management AD" was chosen PowerShell (mostly for the ease of working with .NET and COM).

Code


get unique name of unit

If you have worked with ActiveDirectory or are familiar with other implementing LDAP, you know, as widely used in a variety of instances of unique (distinct) names — DNs (Distinguished Names).

Unique name of LDAP consists of several relatively unique names — RDNs (Relative Distinguished Names), which is a pair of "Attribute = Value". An example of a relative unique name for the Department (Organization Unit):
the
ou=Managers


An example of a unique name for this unit:
the
ou=Managers,DC=example,dc=com


This entry says that in the AD domain with a name "example.com" the first level is the division Managers.

In my experience I can say that the unique names in LDAP are not intuitive and cause beginners some difficulty generalists. So I felt the need to write a simple function that converts an intuitive representation of the "example.com/Managers/Accounting/" in the notation DN:
.SYNOPSIS Takes the path to the OU in the format "example.com/123/456" returns its Distinguished Name. .Description The correctness of the path and its existence is not verified that pozvoyalet to use the function for non-existent paths. NOTE: domain name must be the fully qualified DNS name, not NEIBIOS name (i.e. example.com and not EXAMPLE) .PARAMETER Path The path that will be converted to DN .OUTPUTS System.String. Returns the Distinguished Name corresponding to the transmitted path. #> Function Convert-ADPath2DN([STRING]$Path) { $Res = "" $ResOU = $null #Convert the path to an OU in the OU DN $P = Join-Path $Path "/" $P = $P. Replace("\","/") #Check out if we have something similar to the way if ($P -match "^(?<DNS_DOMAIN_NAME>(\w+\.\w+)+)\/.+$") { $i = 0 $DNS_DOMAIN_NAME = $Matches.DNS_DOMAIN_NAME #Generated path for OU While (-not ($P -eq $DNS_DOMAIN_NAME)) { $i++ $OU = Split-Path -Leaf $P $P = Split-Path -Parent $P If ($i-ne 1) { $ResOU = $ResOU + "," } $ResOU = $ResOU+ "OU=$OU" } } else { $DNS_DOMAIN_NAME = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name } #Change the dots to slashes, it was fashionable to use handy functions for working with paths $DNS_DOMAIN_NAME = $DNS_DOMAIN_NAME.Replace(".","\") $DC_NAMES = @() While ($DNS_DOMAIN_NAME -ne "") { $DC = Split-Path -Leaf $DNS_DOMAIN_NAME $DNS_DOMAIN_NAME = Split-Path -parent $DNS_DOMAIN_NAME $DC_NAMES = $DC_NAMES + $DC } $Count = $DC_NAMES.Count for ($i=$Count;$i-gt 0;$i--) { $DC = $DC_NAMES[$i - 1] If ($i-ne $Count) { $ResDC = $ResDC + "," } $ResDC = $ResDC+ "DC=$DC" if ($ResOU -ne $null) { $Res = $ResOU + "," + $ResDC } else { $Res = $ResDC } } Return $Res }

Example usage:
the
$Var = Convert-ADPath2DN -Path "example.com/Test/"
$Var
OU=Test,DC=example,DC=com


Although this function and has no direct relation to the management of ActiveDirectory, it is very useful and is used in other functions.

organizational unit

Each anycast should know that test scripts need to ActiveDirectory on a dedicated test domain, preferably physically disconnected from the main enterprise network.

No less important is the adherence to the policy of the company to reduce it costs (I think that in many non-core organizations have such instructions) and, therefore, neither PC is able to "pull" a virtual machine with Windows 2003, or, moreover, the individual servers on which to deploy a test environment in anyadike, as a rule, no.

So our hero, in spite of the strict orders of senior colleagues, testing your work right in the "fighting" AD. Not going to condemn him for it, but try instead to help.

The first thing he should do is to create separate Department (Organization Unit), inside which there will be further development of skills scriptorially. Well, as the topic associated with programming in Powershell, implement the appropriate function.

For this we need to use designer Create the class System.DirectoryServices.DirectoryEntry. The final version may look like this:
the
<#
.SYNOPSIS
Creates an organizational unit in AD with the specified name on the specified path. 
.Description
When creating units the existence of the parent is not checked, that can cause problems.
This feature and service. To create units, it is recommended to use the function 
New-ADOrganizationUnit

.PARAMETER Path
The path to the OU in AD which will create a OU (ie the parent).

.PARAMETER Name
The name of the OU (Organization Unit).

.OUTPUTS 
$null. The function does not return a value.
#> 
Function New-ADOrganizationUnit_simple([STRING]$Path,[STRING]$Name)
{
#Disable exception messages since they will occur quite often.
#the main reason is the attempt to create an OU that already exists. 
$ErrorActionPreference = "SilentlyContinue"

#Convert the path to the notation DN (see function description above)
$OUDN = Convert-ADPath2DN -Path $Path 

#Given the path to the standard ADsPath
$OUDN = "LDAP://$OUDN" 

#Create object representing the current domain
$objDomain = [ADSI]$OUDN 

#Create an OU
$objOU = $objDomain.Create("organizationalUnit", "ou=$Name")
$objOU.SetInfo()

#Exception handling
Trap
{
#If the fact that such a unit already has, will inform the user about it. 

{
Write-Host "OU $Name in $Path already exists"
}
}

}


A clear disadvantage of this implementation is the inability of this feature to create a full branch offices: if you need to create a OU "example.com/Users/HR/Women" in the domain in which there is division "example.com/Users" you will not be able to use it to solve this problem.

Or rather, I can, but it will be very inconvenient because you have to first create the OU "Users", then "HR" and then one "Women".

Such a scenario is clearly contrary to the principle of the automation routine, and is therefore unacceptable. Instead, it is much better to use a function that will create the entire thread automatically, for example, this:
the
<#
.SYNOPSIS
Creates an organizational unit in AD for a given path.

.Description
Will be created not only the  last  element, but the whole parent hierarchy (all parent units).

.PARAMETER Path
The path to the output unit.

.OUTPUTS 
$null. The function does not return a value.
#> 
Function New-ADOrganizationUnit ([STRING]$Path)
{
#Check whether the passed  string  a path to an OU (example.com/Unit1/Unit2...)
If ($Path -match "^(?<DNS_DOMAIN_NAME>(\w+\.\w+)+)\/.+$")
{
$i = 0 
#Changing the direction of slashes to use cmdlets "*-Path"
$Pth = Join-Path $Path "/"
$Pth = $Pth.Replace("\","/")

$OUs = @()
#Create OU one
While ($Pth-ne "")
{
$i++
$Pos = $Pth.IndexOf("/") 

If ($i -eq 1)
{
$DNS_DOMAIN_NAME = $Pth.Substring(0,$Pos)
}
else
{
$OU = $Pth.Substring(0,$Pos) 
$OUs = $OUs + $OU
} 
$Pth = $Pth.Substring($Pos+1,($Pth.Length - $Pos -1)) 
}

#Create all the way OU
$Pth = $DNS_DOMAIN_NAME 
For ($i=0;$i -lt $OUs.Count;$i++)
{ 
$Ous = $OUs[$i] 
#Calling the previous function to directly create OU. 
#Units are built top-down, so the situation is creating
#subsidiary if parent is deleted.
New-ADOrganizationUnit_simple -Name $OU -Path $Pth
$Pth = $Pth + "/" + $OU

} 
} 
}


Use of this function is trivial:
the
#Create an OU "Test" and "MyFirstOU" in the domain example.com
New-ADOrganizationUnit -Path "example.com/Test/MyFirstOU/" | Out-Null


add a security group

Seasoned sysadmin, after reading serious books, recommend to your padavana prefer security groups of single user accounts in any administration scripts and AD, especially in the planning of its structure (unless, of course, Padawan generally allow participation in this task).

The argument usually lead replicability of the obtained structures: if, for example, allow reading of a certain group policy to allow the execution of arbitrary ACCORDING to the Director personally (or rather, his account), then when the need arises to provide this opportunity to someone else, you will have all the steps for setting up access rights to repeat again for the other Important Uncle.

If you create a group, for example, "gAllowRunAnything" and give permission to run arbitrary executables to the members of this group, the process is much simpler: it is enough to include an account of the second employee in the group. Obviously, this option is much easier for ancasta (who will have to make these manipulations) and more transparent for the system administrator (who then have to deal with the fact that there namudrili unicast).

Not going to argue with senior colleagues about the importance of groups, and just implement the possibility of establishing them as a function PowerShell:
the
<#
.SYNOPSIS
Creates a group AD in the specified OU. 

.Description
Creates a global security group in the specified OU (Organization Unit).

.PARAMETER Path
The path to the OU in which to create the group.

.PARAMETER Name
The name of the new group

.PARAMETER Force
The modifier created. If it is set, then the path to the OU will be forcibly created.

.OUTPUTS 
$null. This function does not return a value. 
#> 
Function New-ADGroup([STRING]$Path, [STRING]$Name, [System.Management.Automation.SwitchParameter]$Force)
{
#If specified, force creates the OU hierarchy
$ErrorActionPreference = "SilentlyContinue"
If ($Force -eq $true)
{
New-ADOrganizationUnit -Path $Path 

#Convert intelligible to the layperson a representation of the path in DN
$OUDN = Convert-ADPath2DN -Path $Path

#Create object representation of the domain
$OUDN = "LDAP://$OUDN"
$OU = [ADSI]"$OUDN" 

#Create and save the security group in the OU
$Group = $OU.Create("group", "cn=$Name")
$Group.Put("sAMAccountName", "$Name")
$Group.SetInfo() 

#Exception handling
Trap
{
#The most frequently occurring exception, not stopped:
# "this group already exists"
if ($_.Exception.ErrorCode -eq $SYSTEM_ERROR_CODE_OU_ALREADY_EXISTS)
{
Write-Host "Group $Name to $Path already exists"
}
}
}


As you can see here also used one of the versions constructor Create class System.DirectoryServices.DirectoryEntry.

And again using elementary (I specifically tried to simplify the syntax to my colleagues, such as generalists, not having special knowledge in the field of organization and administration AD / LDAP could quickly understand):

the
#Create a group "gSales" in unit "Unit1". If necessary, create the unit itself and all parent units.
New-ADGroup -Path "example.com/Test/MySuperScriptingResults/Fatal/Unit1" -Name "gSales" -Force 


Creating and linking group policy objects

What is active directory? Wikipedia says that is LDAP-compatible implementation of the directory service from MS. The system administrator will probably remember forests, domains and trust, the sat — about the mechanism authentication, and anycast — group policy.

Why about them? In them, the whole professional life of anakasia: installation on the workstations, lock settings IE do not allow users to set different "*Bars", SRP to solve the problem with games in the workplace, and even lock the taskbar that Marivanna likes to move left, requiring anyadike immediately "back as it was, and that it is impossible to work". However, I too delved into memories. I think the thesis of the importance of GPO in Windows networking anybody will not argue.

And if GPO is needed and important, then it should include some mechanisms to work with them in the new library. What should be done with a GPO in the first place? Of course, to create it (obviously, no operation may be preceded by the establishment):
the
<#
.SYNOPSIS
Creates a new GPO in the specified domain. 

.Description
Bind the created object to the subdivision of the organization does not run. Used for work
COM object with GPMC (respectively, the GPMC must be installed).

.PARAMETER DomainDNSName
FQDN of the domain in which the GPO you created.

.PARAMETER GPOName
The name of the GPO.

.OUTPUTS 
GPMGPO. Returns the newly created GPO.
#> 
Function New-GPO([STRING]$DomainDNSName,[STRING]$GPOName)
{ 
$GPM = New-Object -ComObject GPMgmt.GPM 
#List of predefined constants
$GPMConstants = $GPM.GetConstants() 
#Domain object
$GPMDomain = $GPM.GetDomain($DNS_DOMAIN_NAME, $DNS_DOMAIN_NAME, $Constants.UseAnyDC)
#Create the GPO 
$GPMGPO = $GPMDomain.CreateGPO()
$GPMGPO.DisplayName = $GPOName

Return $GPMGPO 
}


This function creates the GPO in the repository objects, and nothing more. It is useful, but for practical ispolzovaniya — insufficient: the created object is required to bind (Link) to the unit. Without this it will remain as "inactive" (technically speaking, it's active, it's not defined impact area). And in order to attach a policy to an OU, you need to get its object representation. It is logical to try to get it, knowing the name of the GPO, for example:
the
<#

Gets a COM object specified GPO on its behalf. 

.Description
Allows you to get fit to modify a GPO, knowing her name. 

.PARAMETER DomainDNSName
FQDN of the domain where the searches GPO.

.PARAMETER GPOName
The name of the GPO that you want to find.

.OUTPUTS 
GPMGPO. Returns a reference to the COM object found GPO ( or $null, if the GPO is not found).
#> 
Function Get-GPO([STRING]$DomainDNSName,[STRING]$GPOName)
{
$GPMGPO = $null
#Disable reactions to emerging exceptions
$ErrorActionPreference = "SilentlyContinue"

#Create object representing the GPMC
$GPM = New-Object -ComObject GPMgmt.GPM 

#List of predefined constants
$GPMConstants = $GPM.GetConstants() 

#Get the GPMDomain object that represents the current domain
$GPMDomain = $GPM.GetDomain($DomainDNSNAme, $DomainDNSNAme, $GPMConstants.UseAnyDC) 

#Looking for group policy in the domain by its name
$GPMGPO = $GPMDomain.SearchGPOs($GPM.CreateSearchCriteria()) | Where-Object{$_.DisplayName -eq $GPOName} 

#Return GPO. If no policy is found, will return $null.
Return $GPMGPO
}


Having search engine GPO name, you can try to bind:
the
<#
.SYNOPSIS
Attach (Link) the GPO to a specific organizational unit (OU). 

.Description
Attaches an existing GPO to an existing OU within the same domain.
Cross-domain joining is not supported.

.PARAMETER DomainDNSName
FQDN of the domain in which the operation is performed joining.

.PARAMETER GPOName
The name of the joining GPO. The object must be available in the repository GPOs for a given domain.

.OUPath PARAMETER
The path to the organizational unit to which you want to attach the specified GPO.

.OUTPUTS 
$Null. This function does not return a result.
#> 
Function Mount-GPOToOU ([STRING]$GPOName,[STRING]$OUPath,[STRING]$DomainDNSName)
{
#Looking for the appropriate GPO in the repository domain
$GPMGPO = Get-GPO-DomainDNSName $DomainDNSName -GPOName $GPOName

#Make sure that the object is found
If ($GPMGPO -ne $Null)
{ 
#Given the path notation DN
$OUDN = Convert-ADPath2DN -Path $OUPath

#Get the representation of the domain in the form of a COM object, GPMC
$GPM = New-Object -ComObject GPMgmt.GPM 
$GPMConstants = $GPM.GetConstants() 
$GPMDomain = $GPM.GetDomain($DomainDNSName, $DomainDNSName, $Constants.UseAnyDC)

#Get the view interesting units
$GPMSOM = $GPMDomain.GetSOM($OUDN) 

#We bind (Link) the GPO to the OU
$GPMSOM.CreateGPOLink(-1, $GPMGPO) | Out-Null 

trap
{
#Exceptions are thrown inside a COM object. We have no way to determine what the cause.
#As a rule, it is that the specified link already exists. So we continue
continue 
}

}
#If the GPO with the given name is not found, tell the user and throw an exception
else
{
Write-Host "Cannot find a GPO object named $GPOName"
Throw "Cannot_find_GPO"
}
}


The latter function allows you to bind not only newly created, but any existing in the repository GPO. One use of this function was the practice of fast "input to production" previously created group policy objects: first, they were tested in separate special organizational unit (OU), and then sudden night at the end of testing and approval from system administrator associated with "combat" units. Be comfortable.

The attentive reader may ask about what your purpose is here used COM object GPMC. It is very simple: .NET provides an extremely high level of abstraction and allows you to perform some operations. For example, I have not found a way to link the GPO to the OU was not able to do the import and export GPO (see below) using the System.DirectoryServices.DirectoryEntry. Using GPMC to perform these actions not only possible, but rather just.

Import and export settings GPO

So, we have the ability to create GPOs and link them to ous. Remember the purpose — why are we even develop all these functions? To help anyadike in his hard work. The nature of assistance needed by unicast? In automation of routine operations which it should perform. Some of them are associated with GPO? Usually, we are talking about introduction of changes developed by the system administrator in the GPO settings in a variety of networks (or in different OU of the same domain).
Usually it happens so: a. decides that, for example, to prevent the "removal" of information to removable media should prohibit the use thereof. The system administrator prepares a description of the relevant GPO settings (manual) and gives an indication of generalists to replicate these changes in their networks.

Changes can be a lot and, even worse, networking too. In the end, it may happen that unicast all day is busy replicating and cannot pull out the jammed sheet from the printer, to provide the desired repertoire for the audio system in the bathroom to find the abstract for my daughter, a student accountant and, worst of all, to show the user where is the "any key".

And here our fighter keyboard and screwdriver come Almighty MS with its mechanism import / export settings GPO, implemented in the framework of the GPMC. This mechanism allows you to import the settings in one GPO to another, even if these two GPOs (donor and recipient) are in different domains. Well, Powershell allows you to bring the process to full automatic.

The export procedure of the reference policy we will leave the system administrator and can handle import. First, consider what is the backup GPO (MS insists that it is Backup and not Export):


The name of the folder in this example is a unique identifier for the exported GPO. We will need it when you import, so now it would be nice to learn this GUID to. Risking to pass for a famous character of the network of folklore, but the easiest way to get the ID just by looking at the folder name, for example:
the
<#
.SYNOPSIS
Allows you to define the GUID of the GPO backup for her (copy) content. 

.Description
The function scans the list of child folders in the backup GPO, finds among them a folder whose name is a GUID, and
returns this value as GUID'and backup. The idea of the method is that a valid backup of the GPO
contains one folder with the name of GUID'. The name of this folder is always consistent with GUID'have a backup.

.PARAMETER Path
Path - the path to the folder that contains the backup GPO. 

.OUTPUTS 
System.String. The GUID of the GPO backup, located at a specified path. 
#> 
Function Get-ExportedBackupGUID([STRING]$Path)
{
#Check if the specified folder, if not - throw an exception
If (-not (Test-Path $Path))
{
Write-Host "Folder $Path does not exist"
Throw "Backup dir path not found" 
}

#Get the list of subfolders
$Children = Get-ChildItem -Force-Literalpath Parameter $Path

#Looking for subfolders in the midst of that which is named GUID'om
Foreach ($Child in $Children)
{ 
If ($Child.FullName -match "^*\{\w{8}\-(\w{4}\-){3}\w{12}\}$")
{
Return $Matches[0]
}

} 
#The folder is available, but backup the GPO in it.
Write-Host "the folder $Path is not found the backup policies" 
Throw "GPO Backup(s) not found" 
}


After exported the GUID of the GPO became known, it is possible to load the settings in any group policy object in the repository AD:
the
<#
.SYNOPSIS
Imports settings from the GPO backup. 

.Description
Allows you to import in the specified group policy object settings saved in the backup objeto GPO. 
We are talking about importe and not restoring from a backup, so you can upload settings from any GPO. 
If the target GPO does not exist, it is created.

.PARAMETER BackupPath
The path to the folder that contains the backup GPO

.PARAMETER DNS_DOMAIN_NAME
FQDN of the domain that is the target policy (the recipient).

.PARAMETER MigrationTablePath
Not used. Reserved for future versions.

.PARAMETER NewGPOName
The name of the GPO to which you want to load settings from a backup. If this parameter is not specified, the name will be taken from


.OUTPUTS 
$null. The function does not return a value.
#> 
Function Import-GPO ([STRING]$BackupPath, [STRING]$DNS_DOMAIN_NAME, [STRING]$MigrationTablePath, [STRING]$NewGPOName = "")
{ 
#Check the availability of the backup folder of GPO. If it is not, throw an exception
If (-not (Test-Path $BackupPath))
{
Write-Host "invalid directory archive GPO: $BackupPath"
Throw "GPO Backup path not found"
}

#Get the COM object GPMC - representation of the domain
$GPM = New-Object -ComObject GPMgmt.GPM 
$GPMConstants = $GPM.GetConstants() 
$GPMDomain = $GPM.GetDomain($DNS_DOMAIN_NAME, $DNS_DOMAIN_NAME, $Constants.UseAnyDC)

#Get the object GPMBackupDir representing the folder with the backup
$GPMBackupDir = $GPM.GetBackupDir($BackupPath) 
#Define exported the GUID of the GPO
$BackupGUID = Get-ExportedBackupGUID -Path $BackupPath
#Get the object representation of the exported GPO
$GPMBackup = $GPMBackupDir.GetBackup($BackupGUID)

#The name of the GPO which will import settings: if not specified, import the object 
#the same name that has a backup
If ($NewGPOName -eq "")
{
$TargetGPOName = $GPMBackup.GPODisplayName
}
else
{
$TargetGPOName = $NewGPOName
}

#Find the repository GPO which will import settings
$GPMGPO = Get-GPO-DomainDNSNAme $DNS_DOMAIN_NAME -GPOName $TargetGPOName

#If the GPO with the specified name is not found, create it
If ($GPMGPO -eq $Null) 
{
$GPMGPO = New-GPO-DomainDNSNAme $DNS_DOMAIN_NAME -GPOName $TargetGPOName
}

#Import the contents of the GPO
$GPMGPO.Import(0, $GPMBackup) | Out-Null 
}



A generalized example of the use of the described functions of the GPO include the following code:

the
##This: in the folder "c:\good_gpo\" backup a group policy object that contains some useful settings created by the system administrator.

#Challenge: to apply these settings to objects (users and computers) located in the unit "example.com/TestUnits/OU1"

##Solution:

# Import settings into a GPO created with the name "Imported_good_GPO" (it will be created optionally).
Import-GPO-BackupPath "c:\good_gpo\" -Dns_Domain_Name "example.com" -NewGPOName "Imported_good_GPO"

#Bind the resulting object to a specified OU
Mount-GPOToOU -GPOName "Imported_good_GPO" -OUPath "example.com/TestUnits/OU1" -DomainDNSName "example.com"



Access GPO

Anyone who's had to deal with the maintenance of Windows domains, AD, certainly, at some point in their career faced with the contradiction of settings specified in the various GPOs that are attached to the same organizational unit.

For example, the OU "Users" which contains the accounts Panovskogo I. I. (head) and Loginova-Parolia V. A. (ancasta) may be attached to two of the group policy object: "pAllow_Run_Any_Executable" allowing, as the name implies, to get everything up and running, and "pDisallow_Run_Anything_Except_Helpdesk", prohibiting the start-up of all, except the customer helpdesk.

Obviously, the first GPO should be applied on account G. Panovskogo without affecting user Loginova-Proleva, and the second — on the contrary. It is equally clear that these GPO are contradictory in nature.

Ie, we need a mechanism to delimit the scope of the specified GPO. You can, of course, to create for one of the defendants in a personal unit (OU) and can bind only the desired GPO, but for such a decision, the sysadmin could hit anyadike a small volume of Chetki on the head. And he is quite right, because with this approach in just a few months the structure of AD will grow so that the admin will not understand.

Much more flexible (and therefore correct) is reshenie based on the distinction of access GPO. In this scheme envisages the establishment of a special security groups governing the right to read and apply each group policy object, and then adding all accounts to the appropriate groups.

For example you could create a group "gAllow_Run_Any_Executable" to give read and apply GPO "pAllow_Run_Any_Executable" only members of this group to include the Manager's account. Policy for ancasta to act similarly.
Thus, the councils of the senior companions, vdolblennye in the head learned in due diligence, we are pushing for a mechanism to control access rights to the GPOs. Well, let's try to implement it.

MSDN teaches us that the right of access to the GPO (Yes, and in General, to any AD object) can be represented in the form of a class object System.DirectoryServices.ExtendedRightAccessRule. This object contains information about the entries in the access rules: who, what to do is allowed or not:
the
#In PowerShell, the object representing the ACE, you can create as follows
$NewRule = New-Object -TypeName System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $objTrustee, $objRihgt, $objACT


Try to understand with the arguments passed via the ArgumentList. Let's start with the obvious: $objTrustee is the security principal to which this rule applies. For example, a user or security group. Simply put, it is "the one to whom belongs the rule". In the construction of "Mashka allowed Vaska touching her own thigh to make changes to the domain sales.example.com" security-principal will be Vaska.

From the point of view of the ordinary anyadike (these employees are, according to the authors, the main users in this article of set of crutches), the easiest way to specify the security principal by its name. But there is a caveat to consider: there are many standard security principals whose names are different in different language versions of Windows. For example, "Administrator" in the English-language version of Windows is called "Administrator".

To solve this problem, MS decided to use special identifiers that are the same in all localizations of OS. We, in turn, just have to learn to use them. The easiest way to use the enumerated type System.Security.Principal.WellKnownSidType:
the
#Get the SID of the domain admins group
$SID = [System.Security.Principal.WellKnownSidType]::AccountDomainAdminsSid


If the entity is not Well-Known (i.e. not included in the list of standard security principals), its representation as an object of class System.Security.Principal.NTAccount to name:
the
[STRING]$FQDN = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
$objTrustee = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList "$FQDN", "$Trustee"


Thus, the view entity (who will allow / deny) we create learned. Now we need to create understanding the law, $objRihgt (what we will allow or deny). In the example above we are talking about the law "change" [domain sales.example.com].

As is known, the number of types of permissions ("read", "write", "delete", etc.) is limited, all are included in the list of valid values of an enumerated type System.DirectoryServices.ActiveDirectoryRightsso the easiest way to create their presentation using the appropriate constructor, for ease of use wrapped into a function:
the

<#
.SYNOPSIS
Creates an object of type System.DirectoryServices.ActiveDirectoryRights.

.Description
Uses the standard constructor of a class System.DirectoryServices.ActiveDirectoryRights.
Written for the convenience of exception handling.

.PARAMETER StrRight
String name of the enumeration System.DirectoryServices.ActiveDirectoryRights.

.OUTPUTS 
System.DirectoryServices.ActiveDirectoryRights, or $null. Returns the object of type System.DirectoryServices.ActiveDirectoryRights, 
sootvetstvuyushie the specified name, or null (if no such object can be created)
#> 
Function Convert-ToAccessRight([STRING]$StrRight)
{
$Res = $null
$ErrorActionPreference = "SilentlyContinue" 
$Res = [System.DirectoryServices.ActiveDirectoryRights]::$StrRight
$ErrorActionPreference = "Stop"
Return $Res
}


It remains only to learn how to create a view of nature of (in the example with Masha and Vaska talking about the word "allowed"), $objACT — ban or permit (after all, each right may be like "this" (Allow) and "selected" (Deny)). Fortunately, this time in MSDN there is a need of an enum type, System.Security.AccessControl.AccessControlTypeso understand the rule type (allow or prohibit) is straightforward:
$objACT = [System.Security.AccessControl.AccessControlType]::Allow #View of the ban $objACT = [System.Security.AccessControl.AccessControlType]::Deny

Consider a practical example of installation specific access permissions to the GPO:
the
<#
This:
You need to prevent team members from "gForbidden_Users" reading GPO "pForbidden_GPO" in domain "Example.com"
#>

#Solution:
#Define the full domain name, we will need it
[STRING]$FQDN = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name
#Create a representation of a group "gForbidden_Users", which we will deny read access
$objTrustee = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList "$FQDN", "gForbidden_Users"

#Create the impression of a deny action
$objActDeny = [System.Security.AccessControl.AccessControlType]::Deny

#Create a view read access
$objRihgt = [System.DirectoryServices.ActiveDirectoryRights]::GenericRead

#Create access rule "to Ban the group 'gForbidden_Users' reading." Later, this rule is applicable to GPO 'gForbidden_Users'
$NewRule = New-Object -TypeName System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $objTrustee, $objRihgt, $objACT

#Create the impression of a GPO by its name.
$GPMGPO = Get-GPO-DomainDNSName "Example.com" -GPOName "pForbidden_GPO"

#Get from a COM object we are interested in policy .NET-object of class System.DirectoryServices.DirectoryEntry
[STRING]$GPOPath = $GPMGPO.Path
$objGPO = New-Object System.DirectoryServices.DirectoryEntry -ArgumentList "LDAP://$GPOPath"

#Add a new entry to the existing rules of access to this GPO
#If you would need to add a few rules, added them one by one
$objGPO.ObjectSecurity.AddAccessRule($NewRule) | Out-Null

#Save the changes.
$objGPO.CommitChanges() | Out-Null 


In principle, this change access rights to the GPO AD completed. However, there is a nuance: the fact is, each GPO has a corresponding folder in a domain-based DFS (\\domanname.example.com\SYSVOL\). And access rights on this folder usually match those set for the GPO. Moreover, if these rights are will vary, GPMC will give the appropriate error:


Such out of sync, if you think about it, is logical: in the above code we are changing the access rights to the GPO without touching the file system. You can, of course, by analogy to add the code to change the permission for folders in the SYSVOL, but there is an easier way. It is based on the fact that GPMC will automatically append permissions on the folder changed the permissions on the GPO. Thus it is enough to make a fake change with the GPMC, and it (the console) itself will take care of the matches:
the
#FQDN is the domain name
$DomainDNSName = "Example.com"
#The name of the GPO for which you want to set permissions
$GPOName = "pForbidden_GPO"

#Get the view of the GPO by its name
$GPMGPO = Get-GPO-DomainDNSName $DomainDNSName -GPOName $GPOName

#Get permissions 
$GPOSecurityInfo = $GPMGPO.GetSecurityInfo()

#We set the same, just received permission.
#The GPMC will automatically set the same permissions for the folder in SYSVOL.
$GPMGPO.SetSecurityInfo($GPOSecurityInfo) | Out-Null 


Opinion


After a period of "running in", I realized that to write (or edit previously written) the scripts for each task to manage the AD — much nicer than to solve the same problem manually. Especially if there is a convenient set of functions for all operations that have to perform.

However, the generalists — people are lazy, and some time writing scripts to perform tasks for administering AD frankly bored. There were thoughts about the further development of the mechanism that is embodied in the creation of a "Universal administration script" that can download job XML-files server system administrators to perform these tasks without my participation (I was able to carry out their duties purely eniseyskie, for example, to enter usernames in the login window, wipe the keyboard, etc.).

A complete description of the resulting mechanism is clearly beyond the scope of this article. I note only the fact that the script has got a modular structure, and one of the modules was CM_ActiveDirectory available with description on PasteBin. To work correctly, he, like all the other modules of this system requires loading the module CM_System, which (along with the appropriate habrahabr.ru

Комментарии

Популярные сообщения из этого блога

Briefly on how to make your Qt geoservice plugin

Database replication PostgreSQL-based SymmetricDS

Developing for Sailfish OS: notifications for example apps for taking notes