Category: Powershell

Some interesting sites:

Reference articles to secure a Windows domain:

Microsoft audit Policy settings and recommendations:

Sysinternals sysmon:!2843&ithint=file%2cpptx&app=PowerPoint&authkey=!AMvCRTKB_V1J5ow


Beyond domain admins:

Gathering AD data with PowerShell:

Hardening Windows computers, secure Baseline check list:

Hardening Windows domain, secure Baseline check list:

Securing Domain Controllers to Improve Active Directory Security



Download sysmon:

NEW: Sysmon 6.10 is available ! :  and how to use it:

NEW: WMI detections:

Installation and usage:

List of web resources concerning Sysmon:

Sysmon events table:

Mark russinovitch’s RSA conference:!2843&ithint=file%2cpptx&app=PowerPoint&authkey=!AMvCRTKB_V1J5ow

Sysmon config files explained:

View story at

Else other install guides:

Sysinternals Sysmon unleashed


Detecting APT with Sysmon:

Sysmon with Splunk:

Sysmon log analyzer/parsing sysmon event log:



logparser GUI:

DNS to change a dynamic record to a static record


DNS PowerShell – import-module DNSServer



DNS Powershell – DnsShell

 Summary: Learn how to use a free Windows PowerShell module to ease administration of Windows DNS: DnsShell (

What is DnsShell?

The majority of the cmdlets in DnsShell are wrappers around the WMI interface. The WMI interface tends to be fairly difficult to work with, or at least more difficult than it needs to be. For the most part this is due to the infamous Generic Error it returns whenever something goes wrong.

In addition to the WMI wrappers, DnsShell contains an interface for working with DNS via LDAP with decoders for the dnsProperty and dnsRecord attributes.

The final cmdlet, Get-Dns, is a DNS resolver, designed to exceed the capabilities of nslookup and to be comparable with Dig.

This post aims to explore some of the capabilities for DnsShell, based on the tasks I use it for on a regular basis.

Basic tasks

Listing zones

Get-DnsZone can be used to return information about zones configured on a server. This cmdlet uses WMI to grab details of the zone. The parameters used with this cmdlet are used to
build a WQL filter.

# All zones


# Primary zones

Get-DnsZone -ZoneType Primary | Format-List

Zone information can be returned from Active Directory using Get-ADDnsZone. By default, Get-ADDnsZone targets the DomainDnsZones application partition.

The information returned by this cmdlet differs slightly from Get-DnsRecord, only showing detail stored in the dnsProperty attribute.

# Returning all zones in DomainDnsZoness (for the current domain)


# Returning all zones from all partitions

Get-ADDnsPartition | Get-ADDnsZone

Listing records

The following examples demonstrate how Get-DnsRecord can be used to pick up records configured on a server.

# Listing all records on the current server


# List A records in domain.example only

Get-DnsRecord -RecordType A -Zone domain.example

# List all static records on the server

Get-DnsRecord -Filter “TimeStamp=0”

# Name is a regular expression and can be used for simple or complex filters

Get-DnsRecord -Name ‘_tcp’ -RecordType SRV

Get-ADDnsRecord may also be to retrieve record information. By its nature Get-ADDnsRecord is limited to querying records stored in Active Directory-integrated zones.

# All records in the DomainDnsZones partition


# All Service (SRV) records in all partitions

Get-ADDnsPartition | Get-ADDnsRecord | Where-Object { $_.RecordType -eq “SRV” }

# Name supports wildcards (used to build an LDAP filter)

Get-ADDnsRecord -Name “_gc*”

# List all records in a specific zone

Get-ADDnsZone domain.example | Get-ADDnsRecord

Unlike Get-DnsRecord, which offers a parameter to filter on RecordType, Get-ADDnsRecord is reliant on Where-Object. This is used because
is no way to construct an LDAP Filter to limit a search to specific record types as the record type is encoded as part of the dnsRecord
attribute. Wildcards or partial matches are not permissible for DnsRecord as it is a Binary Large Object (BLOB).

The RecordType field used above is defined in an Enumeration. The possible values can be seen with:


Using the following filter for Where-Object returns the same results, it is equivalent to the filter above:

# Get all records from AD (DomainDnsZones). Filter to Service Records

Get-ADDnsRecord | Where-Object { $_.RecordType -eq [DnsShell.RecordType]::SRV }

Returning server configuration

Returning a DNS servers configuration is a simple task. The Get-DnsServer cmdlets returns each of the configuration options available to MS DNS server.

# Ask for DNS Server settings from $ServerName

Get-DnsServer $ServerName

Creating zones

New-DnsZone uses the CreateZone method from WMI, checking for a few potential errors along the way.

# A new standard Primary forward lookup zone. Returns an object representing the new zone

New-DnsZone domain.example -ZoneType Primary -PassThru

Creating records

New-DnsRecord is one of the most complex cmdlets in the module. It provides an abstract interface to the CreateInstanceFromPropertyData method on each of the record classes.
Many of the parameters accept pipeline input to further simplify usage.

# A new Host (A) record

New-DnsRecord -Name mail -RecordType A -Zone domain.example -IPAddress

# A new Mail Exchanger (MX) record

New-DnsRecord -RecordType MX -Zone domain.example ‘

-TargetName mail.domain.example -Preference 10

Advanced tasks

Automating secondary zone setup

On occasion it is nice to be able to create secondary zones for every Primary zone on another server.

# Alternate credentials for this operation

$Credential = Get-Credential

# The Primary server name (used to access WMI) and the  IP address $SecondaryServer will use to access the Primary

$PrimaryServer = “ThePrimaryServer”; $PrimaryIP = “”

# A secondary server name (used to access WMI)

$SecondaryServer = “TheSecondaryServer”

# Get all Primary Forward Lookup Zones from $PrimaryServer and create a corresponding Secondary zone on $SecondaryServer

Get-DnsZone -ZoneType Primary -Filter “Reverse=$False” ‘

-Server $PrimaryServer -Credential $Credential |

New-DnsZone -ZoneType Secondary -MasterServer $PrimaryIP  -Server $SecondaryServer -PassThru

A ForEach (either ForEach or ForEach-Object) loop is not necessary to accomplish this, however it may be added if greater control over the process is required.

Get-DnsZone -ZoneType Primary -Filter “Reverse=$False” -Server $PrimaryServer -Credential $Credential |

# See if the zone exists on $SecondaryServer first
If (!(Get-DnsZone $_.Name -Server $SecondaryServer)) {
# If it does not (!), create the zone
$_.Name -ZoneType Secondary -MasterServer $PrimaryIP -Server $SecondaryServer -PassThru

Strictly speaking, the ForEach-Object loop is still not an absolute requirement in this example. Where-Object can be used to filter down to zones that only do not exist on the Primary.

Adding A and PTR records from a CSV file

One common task is the addition of A and corresponding PTR records. To demonstrate this script a simple CSV file can be used.


The tricky part is adding the PTR records without having to manually define the Reverse Lookup Zone name. One possible way to deal with this is to send a
DNS query; an approach is similar to that used by dynamic update, a query which attempts to find a server willing to accept an update.

In the example below, Get-Dns is used to execute a query for the PTR record. The expected response is NXDOMAIN (doesn’t exist), but that response will contain an Authority section including the server and zone name.
That zone name can be used to make the new record.

$ServerName = “TheServer”
$ForwardLookupZone = “domain.example”
Import-Csv Records.csv | ForEach-Object {
# Create the record in the Forward Lookup Zone
New-DnsRecord -Name $_.Name -IPAddress $_.IPAddress -Zone $ForwardLookupZone -Type A -Server $ServerName
# Find the name of the reverse lookup zone
$ReverseLookupZone = (Get-Dns $_.IPAddress -RecordType PTR -Server $ServerName).Authority[0].Name
# Create the record in the Reverse Lookup Zone
New-DnsRecord -Name $_.IPAddress -Hostname “$($_.Name).$ForwardLookupZone” -Zone $ReverseLookupZone -Type PTR -Server $ServerName

Debugging name resolution with Get-Dns

Debugging name resolution under Microsoft Windows is typically limited to either nslookup or the client resolver (normally via ping). Get-Dns is more comprehensive, capable of both simple and complex queries.

Note: The examples below make extensive use of domain.example.
This should be replaced with a valid domain name, many of the examples will return nothing if taken literally.

The following command performs a Zone Transfer, used when a Secondary server picks up a copy of the zone from a Primary Server. This operation should not be confused with replication of zone data when Active Directory-integrated zones are in use. This command requires access to TCP Port 53 on the server holding the zone (most DNS traffic uses UDP Port 53).

# Equivalent of nslookup using ls -d domain.example
Get-Dns domain.example -Transfer
# Or
Get-Dns domain.example axfr

Tracing a request from root using an iterative query; used to verify the delegation chain, to test that DNS requests are directed to the correct servers. The initial servers (Root Hints) are taken from the locally configured DNS service.

# Performs a search for the record from the Root DNS servers (display all hops)
# Equivalent of dig domain.example +trace
Get-Dns domain.example -Iterative

NsSearch can be used to check that all DNS servers for a domain name reply with the same (or intended) answer. If the answers are different clients may experience problems accessing
a resource.

# Returns the A record for domain.example from all authoritative name servers
# Equivalent for dig domain.example a +nssearch
Get-Dns domain.example -RecordType A -NsSearch

Get-Dns returns all of the fields from a DNS packet by default; this makes the return value a complex nested object. Specific parts of the return value can be selected as demonstrated in the following examples.

# Expanding the answer
Get-Dns http://www.domain.example a -NsSearch | Select-Object -ExpandProperty Answer
# If only one response is returned
(Get-Dns http://www.domain.example a).Answer
# Just the Header
Get-Dns domain.example | Select-Object -ExpandProperty Header
# Question, Answer, Authority and Additional are always arrays. An element number must be used when accessing fields even if only one item exists.
(Get-Dns http://www.domain.example a).Question[0].Name
(Get-Dns http://www.domain.example a).Question.Count
(Get-Dns http://www.domain.example a).Question.GetType()
# Answer and Flags
Get-Dns domain.example | Select-Object Answer, @{n=’Flags’;e={ $_.Header.Flags

# The server, status code (RCode) and time taken (in milliseconds) for an Iterative query
Get-Dns domain.exmaple -Iterative | Select-Object Server, TimeTaken,
@{n=’RCode’;e={ $_.Header.RCode }}

The modifications are limited using the Azure management portal, you must use the Powershell module for Azure AD:
Manage Azure AD with Powershell:

First, install the Azure AD powershell cmdlets on a server. It requires the installation of Microsoft Online Services sign-in assistant.

To check the version:
(get-item C:\Windows\System32\WindowsPowerShell\v1.0\Modules\MSOnline\Microsoft.Online.Administration.Automation.PSModule.dll).VersionInfo.FileVersion

To connect to Azure AD:
$msolcred = get-credential                    ; enter the global admin account
connect-msolservice -credential $msolcred

To remove a user: remove-msoluser

To remove a old synchronization user: remove-msoluser
a)    Get-msoluser  ; to display all users
b)    Select the userprincipalname to remove:
c)    Then remove the account:

d)    Get-msoluser   again to control if the user has been deleted

To search a user,
Get-msoluser ; to display all users

To remove a group: remove-msolgroup

But it works using the group’s objectid

To display all groups:
Get-msolgroup –all  ; to list all groups
Get-msolgroup –maxresults  10 ; to list the first 10 groups

To list the number of users and groups:

(Get-msoluser –all).count   ; for all users
And for groups:

To display only the users with license enabled:
Get-msoluser –userprincipalname <account> | ft displayname,licenses

get-msoluser | where {$_.islicensed -like “true”}

To list users with no licenses:
Get-msoluser –userprincipalname | select userprincipalname,islicensed,usagelocation | ft –autosize

For all users:
Get-msoluser | where {$_.isLicensed –like “false”} | ft -autosize

Get-msoluser | where {$_.isLicensed –like “false”} | select userprincipalname,isLicensed,usagelocation | ft -autosize

To list all the users with license enabled:
Get-msoluser | where {$_.isLicensed –like “true”} | select userprincipalname,isLicensed,usagelocation | ft -autosize

To list the SKU available: get-msolaccountsku | ft -autosize

To assign a license to a user:
A)    First you must assign a usage location
get-msoluser -userprincipalname | set-msoluser -usagelocation FR

B)    You can assign a License
Set-MsolUserLicense -UserPrincipalName -AddLicenses “contoso:EMS”

To set a usagelocation FR to all users with no licenses:
Get-msoluser | where {$_.isLicensed -like “false”} | select userprincipalname,isLicensed,usagelocation | set-msoluser -usagelocation FR
And display the result:
Get-msoluser | where {$_.isLicensed -like “false”} | select userprincipalname,isLicensed,usagelocation | ft -autosize

Now assign the contoso:EMS license to all users without license not yet enabled:
Get-msoluser | where {$_.isLicensed -like “false”} | select userprincipalname,isLicensed,usagelocation | set-msoluserlicense -addlicenses “contoso:EMS”
And display the result: Get-msoluser | select userprincipalname,isLicensed,usagelocation | ft -autosize

To search a user based on his userprincipalname:
Get-msoluser –all | where {$_.userprincipalname –like “”} | select userprincipalname,islicensed,usagelocation
Adding/removing members from another forest or domain to groups in Active Directory:

Example of powershell script:
Write-Host “Loading the Quest.ActiveRoles.ADManagement powershell snap-in”
if ( (Get-PSSnapin -Name Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue) -eq $null )
Add-PsSnapin Quest.ActiveRoles.ADManagement

Write-Host “”
$rootOU = “DC=mydomain,DC=local”
$date = Get-Date -Format ddMMyyyy
$log = “.\Update-CVS-GroupMembership-$date.txt”
$startscript = Get-Date
$totalgrp = 0
$nceoldgrp = 0
$changes = 0
$KO = 0
Write-Host “”
Write-Host “————————————————————————————————”
Write-Host “”
Get-QADGroup -SearchRoot $rootOU | %{
$members = $null
$groupname = $_.Samaccountname
$members = Get-QADGroupMember -Identity $_.DN -Type group -Name MII_*
if ($members -ne $null) {
foreach ($member in $members) {
$GroupToReplace = $member.Samaccountname
$GroupToFind    = $GroupToReplace -replace (“MII_”,””)
$GroupExist = $null

$GroupExist = Get-QADGroup -SearchRoot “OU=Groups,DC=mydomain,DC=local” -SearchScope OneLevel -SamAccountName $GroupToFind

If ($GroupExist -ne $null)
Add-QADGroupMember -Identity $groupname -Member $GroupExist.DN -proxy
# Remove-QADGroupMember -Identity $groupname -Member $member.DN -proxy
Write-Output “Modification – $GroupToReplace has been replaced by $GroupToFind in the $groupname” | Out-File $log -Append
Write-Output “Error – $groupname unchanged… $GroupToReplace has not a matching group as $GroupToFind”  | Out-File $log -Append
Write-Host “”
Write-Host “–STATISTICS–” -BackgroundColor Blue -ForegroundColor White
Write-host “TOTAL “$totalgrp” total groups parsed” -BackgroundColor Yellow -ForegroundColor Black
Write-host “TOTAL “$nceoldgrp” total old MII_xxx groups found” -BackgroundColor Yellow -ForegroundColor Black
Write-host “TOTAL “$changes” total groups changed successfully” -BackgroundColor Yellow -ForegroundColor Black
Write-host “TOTAL “$KO” total groups with no matching” -BackgroundColor Yellow -ForegroundColor Black
Write-Host “”
#Start-Sleep 5
Write-Host “——————-”
Write-Host “– End of Script –”
Write-Host “——————-”
Write-Host “”
$stopscript = Get-Date
Write-Host “Has started at” $startscript -BackgroundColor Gray -ForegroundColor Black
Write-Host “Had finished at” $stopscript -BackgroundColor Gray -ForegroundColor Black
Write-Host “TIME SPENT:” (New-TimeSpan -Start $startscript -End $stopscript).hours “Hours” (New-TimeSpan -Start $startscript -End $stopscript).minutes “Minutes” (New-TimeSpan -Start $startscript -End $stopscript).seconds “Seconds” -BackgroundColor Green -ForegroundColor Black
Write-Host “”
Write-Host “”


The NSA released a PDF entitled “Spotting the Adversary with Windows Event Log Monitoring” earlier this year. The good news is it’s probably one of the most detailed documents I’ve seen in a long time. Everything from setting up Event Subscriptions, to a hardened use of Windows Remote Management, including the use of authentication and firewalls, this document tells you how to securely setup an environment where you can natively consolidate and monitor event log based entries. In addition, the NSA goes onto cover a number of areas that should be monitored – complete with event IDs:

Event forwarding guidance:

Malware archeology cheat sheets:

Machine-specific issues – which can be indications of malicious activity

  • Application Crashes
  • System or Service Failures
  • Kernel and Device Signing
  • The Windows Firewall

Administrator Activity – specific actions performed that may be suspect

  • Clearing of Event Logs
  • Software and Service Installation
  • Remote Desktop Logon
  • Account Usage

The bad news is you’re still left to sort out a TON of event log detail and interpret whether the entries are a problem or not.

Additionally: Changes to Group Policy only show up in the events as a change to the policy, but lack detail on exactly what was changed within the Group Policy.

To truly have a grasp on whether you have an “adversary” within or not and, if so, what that adversary is doing, you’re going to require a solution that not only collects events, but can correlate them into something intelligent. Your solution should:

  • Consolidate events
  • Focus on the events you are concerned about
  • Provide comprehensive detail about the changes to your systems, security and data

Three software solutions:

  • Netwrix Auditor for AD
  • Dell change auditor for AD
  • IBM QRadar (SIEM)

Splunk (SIEM)  : Splunk Windows Auditing using the NSA guide:

MS white-paper best practices to secure AD:

MS Advanced threat analytics (MS ATA):

Windows Event IDs useful for intrusion detection:

Windows Vista events and above

Category Event ID Description
User Account Changes 4720 Created
4722 Enabled
4723 User changed own password
4724 Privileged User changed this user’s password
4725 Disabled
4726 Deleted
4738 Changed
4740 Locked out
4767 Unlocked
4781 Name change
Domain Controller Authentication Events 4768 TGT was requested
4771 Kerberos pre-auth failed
4772 TGT request failed
Logon Session Events 4624 Successful logon
4647 User initiated logoff
4625 Logon failure
4776 NTLM logon failed
4778 Remote desktop session reconnected
4779 Remote desktop session disconnected
4800 Workstation locked
4801 Workstation unlocked
Domain Group Policy 4739 Domain GPO changed
5136 GPO changed
5137 GPO created
5141 GPO deleted
Security 1102 Event log cleared
Software and Service Installation 6 New Kernel Filter Driver
7045 New Windows Service
1022, 1033 New MSI File Installed
903, 904 New Application Installation
905, 906 Updated Application
907, 908 Removed Application
4688 New Process Created
4697 New Service Installed
4698 New Scheduled Task
External Media Detection 43 New Device Information
400 New Mass Storage Installation
410 New Mass Storage Installation
Group Changes Created Changed Deleted Members
Added Removed
Security Local 4731 4737 4734 4732 4733
Global 4727 4735 4730 4728 4729
Universal 4754 4755 4758 4756 4757
Distribution Local 4744 4745 4748 4746 4747
Global 4749 4750 4753 4751 4752
Universal 4759 4760 4763 4761 4762

Monitoring ADFS and the AAD Connect Sync Engine using AAD connect health:


Remotely enable PSRemoting and Unrestricted PowerShell Execution using PsExec and PSSession, then run PSRecon

Option 1 — WMI:
PS C:\> wmic /node:”″ process call create “powershell -noprofile -command Enable-PsRemoting -Force” -Credential Get-Credential

Option 2 – PsExec:
PS C:\> PsExec.exe \\ -u [admin account name] -p [admin account password] -h -d powershell.exe “Enable-PSRemoting -Force”


PS C:\> Test-WSMan
PS C:\> Enter-PSSession
[]: PS C:\> Set-ExecutionPolicy Unrestricted -Force


Option 1 — Execute locally in-memory, push evidence to a share, and lock the host down:
[]: PS C:\> IEX (New-Object Net.WebClient).DownloadString(‘;)
[]: PS C:\> Copy-Item PSRecon_* -Recurse [network share]
[]: PS C:\> rm PSRecon_* -Recurse -Force
[]: PS C:\> Invoke-Lockdown; exit

Option 2 — Exit PSSession, execute PSRecon remotely, send the report out via email, and lock the host down:
[]: PS C:\> exit
PS C:\> .\psrecon.ps1 -remote -target -sendEmail -smtpServer -emailTo greg.foss[at] -emailFrom psrecon[at] -lockdown

Be careful! This will open the system up to unnecessary risk!!
You could also inadvertently expose administrative credentials when authenticating to a compromised host.
If the host isn’t taken offline, PSRemoting should be disabled along with disallowing Unrestricted PowerShell execution following PSRecon

Import-module ActiveDirectory

$rootOU = “OU=Special Users,OU=Users,DC=MyDomain,DC=com”

$group = “mydomain\group1”

Get-ADUser -SearchBase $rootOu -Filter * | ForEach-Object {Add-ADGroupMember -Identity $group -Members $_ }

You can Schedule this script to run every hour.






PowerShell Variables:

TechNet Magazine:

$user = “mydomain\user1”
if ((Get-QADUser $User -Properties memberof).memberof -like “CN=finance-group*”)
write-host “User found” $true
write-host “User found” $false