My PowerShell Scripts Repository

Write-PSscripts | Blog-Them

Powerquery is … powerfull !!

This is definitly not directly related to PowerShell, but I have to note it somewhere 🙂

Here are some Powerquery samples to parse 4624 events :



Text.Range(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Security ID:”, Occurrence.Last)+14),0,Text.PositionOf(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Security ID:”, Occurrence.Last)+14),”#(cr)”, Occurrence.First))


Text.Range(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Account Name:”, Occurrence.Last)+15),0,Text.PositionOf(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Account Name:”, Occurrence.Last)+15),”Account Domain:”, Occurrence.First))


Text.Range(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Account Domain:”, Occurrence.Last)+17),0,Text.PositionOf(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Account Domain:”, Occurrence.Last)+17),”Logon ID:”, Occurrence.First))


Text.Range(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Workstation Name:”, Occurrence.Last)+18),0,Text.PositionOf(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Workstation Name:”, Occurrence.Last)+18),”Source Network Address:”, Occurrence.First))


Text.Range(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Source Network Address:”, Occurrence.Last)+24),0,Text.PositionOf(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Source Network Address:”, Occurrence.Last)+24),”Source Port:”, Occurrence.First))


Text.Range(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Logon Process:”, Occurrence.Last)+16),0,Text.PositionOf(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Logon Process:”, Occurrence.Last)+16),”Authentication Package:”, Occurrence.First))


Text.Range(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Authentication Package:”, Occurrence.Last)+24),0,Text.PositionOf(Text.RemoveRange([Message],0,Text.PositionOf([Message],”Authentication Package:”, Occurrence.Last)+24),”Transited Services:”, Occurrence.First))

AdminSDHolder protected objects

Want to know which groups or users are protected throuh AdminSDHolder process ?

You can have a look to the protected groups with the following commands:

Get-ADgroup -LDAPFilter “(admincount=1)” | select name,SID

And here is the one for the users:

Get-ADuser -LDAPFilter “(admincount=1)” -Properties * | select name, memberOf


Just a last thing… D Day is coming in 10…

Deploy a Data Collector Set to domain controllers of several forests…

I’ve created a new data collector set for my domain controllers, and I want now to be able to deploy it broadly.

So I’ve created a Powershell script to deploy it to all domain controllers in several forests or domains. Please notice this is a first released version, and there is no error management yet. The script will copy the xml file to the destination server (directly on c$, need also to improve that!), and then will add it as a DCS. You need WinRM to be enabled on your servers (winrm quickconfig). Obviously, you’ll need permissions to deploy DCS to domain controllers (I made my test being added to Administrators group of each domain I used.)

As an input for my script, you’ll need to provide the XML template, so you’ll need to export it as a template, through the Performance Monitor GUI.


Once you have the XML file, you’’ll need to customize the script to provide the forests you want to deploy it, customizing the following line:

$forests = @(“forest1.ext,forest2.ext,forest3.ext”)

Once you’ve done that, you can also notice I use a default DCS for my test in the following line:

$DCSxmltemplate = “C:\DCS\DCS-DomainControllers-V1.2.xml”

You should remove the default value and set the parameter mandatory.

And…here is the script:

Deploy Data Collector Set on Domain Controllers
Create a New Perfmon Data Collector Set from an XML template
Use PowerShell remoting to create these on a remote server.
Remoting must be enabled on target servers (winRM)
Authors:&nbsp; Jean-Philippe Klein
[parameter(Mandatory=$False,HelpMessage=’XML Template of Data Collector Set’)]
$DCSxmltemplate = “C:\DCS\DCS-DomainControllers-V1.2.xml”
### Overall Timing
$ScriptStart = get-date

### Test for variables
if ((Test-Path $DCSxmltemplate) -ne $true) {
    Write-Host “Path to XML file is invalid, exiting script”

### Work with variables
$DCSxmlfilename = $DCSxmltemplate.split(“\”)[-1]
$DCSname = $DCSxmlfilename.Substring(0,$DCSxmlfilename.Length-4)

write-host “— Script Info —“
write-host “Template: $DCSxmltemplate”
write-host “XML File: $DCSxmlfilename”
write-host “DCS Name: $DCSname”

### Load Module
Import-Module ActiveDirectory

### Add support for all forests
$forests = @(“forest1.ext,forest2.ext,forest3.ext”)
foreach ($forest in $forests) {

    ### Get Domain Controllers List
    $DCList = Get-ADDomainController -filter * -server $forest | select-object HostName

    ### Script
    foreach ($DC in $DCList) {
        $server = $DC.Hostname.ToString()
        write-host “— Execution on $server —“
        ### Copy template to c:\ of remote server
        copy-item $DCSxmltemplate “\\$server\c$”
        $remotexml= “c:\$DCSxmlfilename”
        ### Inject DCS to remote server
        Invoke-Command -ComputerName $server -ArgumentList $DCSname,$remotexml -ScriptBlock{Param($DCSName,$remotexml)
            $DCS = new-object -COM Pla.DataCollectorSet
            $xml = Get-Content “$remotexml”
            $DCS.Commit(“$DCSName”, $null , 0x0003) | Out-Null
        ### Remove template of remote server
        $remotepath = “\\” + $server + “\c$\” + $DCSxmlfilename
        Remove-item  -path $remotepath -force

### Overall Timing
$ScriptStop = get-date
($ScriptStop – $ScriptStart).ToString()

HTH, I’ll update this one with several improvements soon (Error management, Temp folder customization…)

Here are 2 useful links to help dealing with DCS and PowerShell:

Want your Script tells you everything is ok ?

Yes, you can ! PowerShell is able to use the Speech API and to tell you everything you want. Here is how:

$VoiceObj = new-object -com SAPI.SpVoice
$VoiceObj.Speak( "The script ended", 1 )

You want to read the txt output file just generated ? You can:

$VoiceObj = new-object -com SAPI.SpVoice
$VoiceObj.Speak( "C:\PS\Output.txt", 5 )

Check the list of your domain controllers OS version:

If you plan for a DFL upgrade, you’ll need to check you have not forgotten a DC in a previous OS version. To list all your domain controller OS versions, you can use the following command (with ActiveDirectory module):

Foreach ($dc in ((get-addomain).replicaDirectoryServers))  { (Get-AdDomainController $dc).OperatingSystem }

Deny read/apply permissions on a GPO

Question of the day is how to deny the “read’ and “apply” permissions on a GPO through PowerShell ? Please don’t ask my why I would do that, because this is another (long) discussion I’ve already had with several colleagues. (Kudos to Pierre and Thomas for their availability around this subject.)

So, first, let’s go deeper in what’s happening when you change the security of a GPO with GPMC.

Here are the settings applied for group “gs_Users_BetaProgram001” in GPMC advanced window:


Let’s check it now with PowerShell (using both ActiveDirectory and GroupPolicy modules).

First, what is the GUID of the group we used ?

PS C:\> $strGroup = "gs_Users_BetaProgram001"
PS C:\> $GroupObject = Get-ADGroup $strGroup
PS C:\> $GroupSid = new-object System.Security.Principal.SecurityIdentifier $GroupObject.SID
PS C:\> $GroupSid.value


Now, let’s check what are the permissions on the GPC ?

PS C:\> $strGPO = "USR_GPO001_IESettings"
PS C:\> $GPOObject = Get-GPO $strGPO
PS C:\> $GPOPath = $GPOObject.path
PS C:\> $GPOADObject = [ADSI]"LDAP://$GPOPath"
PS C:\> $GPOObjSec = $GPOADObject.psbase.ObjectSecurity
PS C:\> $GPOACLList = $GPOObjSec.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])

ActiveDirectoryRights : ReadProperty, GenericExecute
InheritanceType       : None
ObjectType            : 00000000-0000-0000-0000-000000000000
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : None
AccessControlType     : Deny
IdentityReference     : S-1-5-21-175584704-1530855495-729742623-15091
IsInherited           : False
InheritanceFlags      : None
PropagationFlags      : None

ActiveDirectoryRights : ExtendedRight
InheritanceType       : None
ObjectType            : edacfd8f-ffb3-11d1-b41d-00a0c968f939
InheritedObjectType   : 00000000-0000-0000-0000-000000000000
ObjectFlags           : ObjectAceTypePresent
AccessControlType     : Deny
IdentityReference     : S-1-5-21-175584704-1530855495-729742623-15091
IsInherited           : False
InheritanceFlags      : None
PropagationFlags      : None

[…] Only the two first results are about S-1-5-21-175584704-1530855495-729742623-15091 which is the group we’re looking for.

We can observe there are several rights : ReadProperty, GenericExecute and ExtendedRight

The ExtendedRight is defined by the ObjectType, which is edacfd8f-ffb3-11d1-b41d-00a0c968f939. This one means “Apply-Group-Policy” (

Let’s go further with the permissions on the GPT ?

PS C:\> $GPOGPTstr = "\\"+$GPOObject.DomainName+"\SYSVOL\"+$GPOObject.DomainName+"\Policies\{"+$GPOObject.Id+"}"
PS C:\> $acl = Get-ACL $GPOGPTstr
PS C:\> $acl.GetAccessRules($true, $true, [System.Security.Principal.NTAccount])

FileSystemRights  : ReadAndExecute
AccessControlType : Deny
IdentityReference : FABRIKAM\gs_Users_BetaProgram001
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

[…] Only the first result is about gs_Users_BetaProgram001 group.

We can conclude that permissions are applied both to GPC and GPT. More investigations are needed here, but it seems it is a tool thing (GPMC).

So, let’s create a new GPO, called USR_GPO003_xxx, and use the following script to apply the Deny-Apply and Deny-Read permissions for the group called gs_Users_BetaProgram001.

$strGroup = "gs_Users_BetaProgram001"
$strGPO = "USR_GPO003_xxx"

$GroupObject = Get-ADGroup $strGroup
$GroupSid = new-object System.Security.Principal.SecurityIdentifier $GroupObject.SID
$GPOObject = Get-GPO $strGPO

$GPOPath = $GPOObject.path
$GPOADObject = [ADSI]"LDAP://$GPOPath"
$GPOObjSec = $GPOADObject.psbase.ObjectSecurity
$GPOACLList = $GPOObjSec.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])

$extRight = [system.guid]"edacfd8f-ffb3-11d1-b41d-00a0c968f939"
$ace1 = new-object System.DirectoryServices.ActiveDirectoryAccessRule $GroupSid,"ReadProperty, GenericExecute","Deny","None"
$ace2 = new-object System.DirectoryServices.ActiveDirectoryAccessRule $GroupSid,"ExtendedRight","Deny",$extRight,"All"

$GPOGPTstr = "\\"+$GPOObject.DomainName+"\SYSVOL\"+$GPOObject.DomainName+"\Policies\{"+$GPOObject.Id+"}"
$acl = Get-ACL $GPOGPTstr

$acl.SetAccessRuleProtection($True, $False)
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($strGroup,"ReadAndExecute", "ContainerInherit, ObjectInherit", "None", "Deny")
Set-Acl $GPOGPTstr $acl

You can now check, permissions are applied to both GPC and GPT:


And this is what I was looking for, applying both deny read and deny apply permissions on a GPO for a specific group, programmatically.

Question of the day seems solved. HTH.


Windows 8–PowerShell ISE is… so cool !

What a nice surprise when launching ISE on my W8 laptop:


That just rocks… I looked then for more information about new features and capabilities of the new ISE, and the list is long, trust me… or go have a look here “Windows PowerShell ISE v3.0, Now with Extra AWESOME!”.

Another reason to install Windows 8 !

Add a get-help feature to your own scripts

If you want to share your scripts (or reuse them several months later), you’ll need to add comments and description into the code. To be consistent, you can use the built-in help provided by PowerShell.

You only need to add a block comment on the beginning of the script, and use the following section keywords:

  • .SYNOPSIS –a brief explanation of what the script or function does.
  • .DESCRIPTION – a more detailed explanation of what the script or function does.
  • .PARAMETER name – an explanation of a specific parameter. Replace name with the parameter name. You can have one of these sections for each parameter the script or function uses.
  • .EXAMPLE – an example of how to use the script or function. You can have multiple .EXAMPLE sections if you want to provide more than one example.
  • .NOTES – any miscellaneous notes on using the script or function.
  • .LINK – a cross-reference to another help topic; you can have more than one of these. If you include a URL beginning with http:// or https://, the shell will open that URL when the Help command’s –online parameter is used.
  • You can have a look to the output of the help about_comment_based_help command to get more information. I also encourage you to read the following post from Don Jones : WTFM: Writing the Fabulous Manual

    He is an example:

    Retrieves service pack and operating system information from one or more remote computers.

    The Get-Inventory function uses Windows Management Instrumentation (WMI) toretrieve service pack version, operating system build number, and BIOS serial number from one or more remote computers.
    Computer names or IP addresses are expected as pipeline input, or may bepassed to the –computerName parameter.
    Each computer is contacted sequentially, not in parallel.

    .PARAMETER computerNameAccepts
    a single computer name or an array of computer names. You mayalso provide IP addresses.

    .PARAMETER path
    The path and file name of a text file. Any computers that cannot be reached will be logged to this file.
    This is an optional parameter; if it is notincluded, no log file will be generated.

    Read computer names from Active Directory and retrieve their inventory information.
    Get-ADComputer –filter * | Select{Name="computerName";Expression={$_.Name}} | Get-Inventory.

    Read computer names from a file (one name per line) and retrieve their inventory information
    Get-Content c:\names.txt | Get-Inventory.

    You need to run this function as a member of the Domain Admins group; doing so is the only way to ensure you have permission to query WMI from the remote computers.