Copy windows features from a server to another

Sometimes you want to create the (almost) same server where you do not yet Chef or CF or some sort of DSC. The best resort is to use what you have: get-windowsfeature

Imagine you want to configure Server B from Server A and obviously those are microsoft windows servers…

#On server A
#export features
> Get-WindowsFeature | ? { $_.Installed -AND $_.SubFeatures.Count -eq 0 } | Export-Clixml .\serverA.xml
#copy the feature file over
> cp .\serverA.xml ‘\\serverB\c$\Files’

#On server B
PS C:\Files> ls
Directory: C:\Files
Mode LastWriteTime Length Name
—- ————- —— —-
-a—- 6/8/2017 4:15 PM 510824 ServerA.xml
PS C:\Files> Import-Module Servermanager
PS C:\Files> Import-Clixml .\ServerA.xml | Add-WindowsFeature
Success Restart Needed Exit Code Feature Result
——- ————– ——— ————–
True Yes SuccessRest… {Application Server, .NET Framework 4.5, W…
WARNING: You must restart this server to finish the installation process.

Voila, another posh timesaver.


#clone, #get-windowsfeature, #powershell, #server, #windows-2

Exchange Message Tracking Statistics for Zabbix

I surely missing something but somehow I could not find a way to easily retrieve statistics of Sent and Received messages from Exchange 2013 (SP1 with DAG). I first looked into the performance counters but I could not make sense of all of the MSExchangeTransport – or too lazy to research them up.

typeperf -qx | findstr /ic:MSExchangeTransport

And usually if I am tracking some email flooding or prior to investigating the queues, I go use the get-messagetrackinglog. And so I create a short script to gather the list of the last X minutes of messages, count them, make them available and them to zabbix using zabbix_sender.

So it goes like this:

#import snapin
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010

#list of transport servers
$hts = "EchangeTransport1","EchangeTransport2"
#start one Hour ago from now
$start = (get-date).AddMinutes(-30)
$end = (get-date)
#get all logs
$logs = $hts |% {get-messagetrackinglog -start $start -end $end -server $_ -resultsize unlimited}
#clear stats
$stats = ""| select sent,received
#count Deliver and Send
$logs |% {
if ($_.eventid -eq "Deliver"){[int]$stats.received += 1}
if ($_.eventid -eq "Send"){[int]$stats.sent+= 1}
#Display results for debug and info, comment or remove if not needed
$stats | ft -auto > LastCount.log
get-date >> LastCount.log

#cannot run the above using zabbix/system account on exchange
#use zabbix_sender
C:\zabbix\bin\win64\zabbix_sender.exe -z zabbixIP  -s $hts -k Stats.RxMessageCount -o $stats.received
C:\zabbix\bin\win64\zabbix_sender.exe -z zabbixIP -s $hts -k Stats.TxMessageCount -o $stats.sent

It is short and easy but that there some things to do in Zabbix and it can store the sent values, as per above Stats.RxMessageCount and Stats.TxMessageCount.

I went into Zabbix>Configuration>Templates to edit the template I had created to keep all of the Exchange things I monitor. Select the item screen and clicked that “Create Item” button.


Then the most important is the Type which must be Zabbix Trapper, the rest is up to you.I also chosen a “Unit” and created a new application “Exchange 2013 Statistics”.


Once the item is create, do the same for the other value. Altogether you’ll end up with 2 new items under the template.

Provided this template is assigned to your exchange host you are running the above script from, the values will be fed to Zabbix accordingly.

I actually set up a scheduled task that matches the timing and now I have some trending of the Sent and Received messages as per the Message Tracking Logs – Yeah it includes the HealthMonitor traffic, I know.

Additionally and once you have a baseline, you can also create a trigger based on the value received.

#exchange, #message, #messagetracking, #monitoring, #powershell, #send, #zabbix

Managing Certificates using Powershell

Because of my recent work with ADFS I was looking for a way to automate most of the certificate configuration by scripts. The usual run-books I write would usually include the use of the mmc and a bunch of screenshot to accompany them.

The answer is that powershell management for Certificates is there and here are some examples:


#Powershell exposes certs under cert:\
PS C:\> Get-PSDrive
Name Used (GB) Free (GB) Provider Root CurrentLocation
—- ——— ——— ——– —- —————
A FileSystem A:\
Alias Alias
C 14.37 45.29 FileSystem C:\
Cert Certificate \
D FileSystem D:\
Env Environment
Function Function
Variable Variable
PS C:\> cd cert:
PS Cert:\> dir localmachine
Name : TrustedPublisher
Name : ClientAuthIssuer
Name : Remote Desktop
Name : Root
Name : TrustedDevices
Name : CA
Name : AuthRoot
Name : TrustedPeople
Name : My
Name : SmartCardRoot
Name : Trust
Name : Disallowed
Name : AdfsTrustedDevices

#Browsing through the stores is pretty intuitive
PS Cert:\> dir Cert:\LocalMachine\My
Directory: Microsoft.PowerShell.Security\Certificate::LocalMachine\My
Thumbprint Subject
———- ——-
E31234DEF282437D167A64FD812342B650C20B42 CN=XXXXa
8912343319B07131C8FD1234E250DC67CBE08D7A CN=XXXX
69AD2C21912340919D186503631234A6F0BE9F7F CN=*,XXX..

#Exporting a cert is something a little less intuitive
PS Cert:\> $ExportCert = dir Cert:\LocalMachine\Root | where {$_.Thumbprint -eq “892F212349B07131C12347F8E250DC67CBE08D7
PS Cert:\> $ExportCryp = [System.Security.Cryptography.X509Certificates.X509ContentType]::pfx
PS Cert:\> $ExportKey = ‘pww$@’
PS Cert:\> $ExportPFX = $ExportCert.Export($ExportCryp, $ExportKey)
PS Cert:\> [system.IO.file]::WriteAllBytes(“D:\Temp\CertToExportPFXFile.PFX”, $ExportPFX)

#same mess for importing

  1. Define The Cert File To Import

$CertFileToImport = “D:\Temp\CertToImportPFXFile.PFX”

  1. Define The Password That Protects The Private Key

$PrivateKeyPassword = ‘Pa$$w0rd’

  1. Target The Cert That Needs To Be Imported

$CertToImport = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $CertFileToImport,$PrivateKeyPassword

  1. Define The Scope And Certificate Store Within That Scope To Import The Certificate Into
  2. Available Cert Store Scopes are “LocalMachine” or “CurrentUser”

$CertStoreScope = “LocalMachine”

  1. For Available Cert Store Names See Figure 5 (Depends On Cert Store Scope)

$CertStoreName = “My”
$CertStore = New-Object System.Security.Cryptography.X509Certificates.X509Store $CertStoreName, $CertStoreScope

  1. Import The Targeted Certificate Into The Specified Cert Store Name Of The Specified Cert Store Scope


For import/export, I’d recommend using code from here:


#certificate, #certs, #manage, #pfx, #stores

Get a List of All Useless Group Policy Objects

I have another problem today. The problem is that the previous Group Policy administrator had no strategy. I have been the chosen one to clean up our Group Policy strategy. As a result there are bunch of Group Policy objects (GPOs) that go nowhere or do nothing.

I had noticed this powershell guys article, but it looked like utterly complex.

import-module grouppolicy 
function IsNotLinked($xmldata){ 
    If ($xmldata.GPO.LinksTo -eq $null) { 
        Return $true 
    Return $false 

Function IsEmpty($xmldata){
    If ($xmldata.GPO.Computer.VersionDirectory -eq 0 -and $xmldata.GPO.User.VersionDirectory -eq 0) { 
        Return $true 
    Return $false 
$unlinkedGPOs = @() 
$emptyGPOs = @() 

#Search for NotLinked GPOs
Get-GPO -All | ForEach { $gpo = $_ ; $_ | Get-GPOReport -ReportType xml | ForEach { If(IsNotLinked([xml]$_)){$unlinkedGPOs += $gpo} }} 
If ($unlinkedGPOs.Count -eq 0) { 
    "No Unlinked GPO's Found" 
	$unlinkedGPOs | Select DisplayName,ID | ft 
	$unlinkedGPOs | backup-GPO -path S:\ActiveDirectory\GPOBackups | select DisplayName,GpoID, BackupDirectory | ft
	$unlinkedGPOs | remove-gpo -Confirm

#Search for Empty GPOs
Get-GPO -All | ForEach { $gpo = $_ ; $_ | Get-GPOReport -ReportType xml | ForEach { If(IsEmpty([xml]$_)){$emptyGPOs += $gpo} }} 

If ($emptyGPOs.Count -eq 0) { 
    "No Empty GPO's Found" 
	$emptyGPOs | Select DisplayName,ID | ft 
	$emptyGPOs | backup-GPO -path S:\ActiveDirectory\GPOBackups | select DisplayName,GpoID, BackupDirectory | ft
	$emptyGPOs | remove-gpo -Confirm

It is to be used simply and get some output list – or uncomment the warranty info and backup and then delete the nasty stuff.

DisplayName                                                 Id
———–                                                 —
Disable Outlook Cache                                       7aca484c-ebcd-4779-9bc8-b2fb8e7302d1
Turn Outlook Junk Mail Filter Off                           de14544c-39be-444f-ac53-089ca0bc65a8
Microsoft Office Trust Centre                               ed6fb632-fdd2-4718-96b0-b3981b4145bd

DisplayName                                                 Id
———–                                                 —
Portal Home page — mandatory                               bbc9efe7-05c3-4187-92ac-948772f50bf8

Please note that GPO backup ID during the Backup-gpo is not the GPOID!

#active-directory, #ad, #clean-up, #gpo, #powershell

Import PST to mailboxes – Exchange 2013

For this recipe you will need:

  • a pst archive file stored on a UNC accessible location
    New-ManagementRoleAssignment –Role "Mailbox Import Export" –User Administrator
  • be granted the “mailbox import export” management role

when all this is gather, the import is pretty simple.

To import the pst file into a specific mailbox do:

[PS] C:\>New-MailboxImportRequest -FilePath \\SERVER\share$someone.pst -Mailbox someone

you can also import a pst to someone else mailbox into a specific folder:

[PS] C:\>New-MailboxImportRequest -FilePath \\SERVER\share$someone.pst -Mailbox someoneelse -TargetRootFolder "ImportedMailbox from someones PST" 

Once submitted you can follow the request status with this command:

[PS] C:\>Get-MailboxImportRequest -Mailbox someone| Get-MailboxImportRequestStatistics

Name                                   StatusDetail              TargetAlias                           PercentComplete
----                                   ------------              -----------                           ---------------
MailboxImport                          CopyingMessages           someone            92

Removing any switch to the get-mailboximportrequest will show status for all requests.

Shall it fail and you need further information, you shall use the following to examin the reason and pipe it to a file for something more legible:

[PS] C:\>Get-MailboxImportRequest -Mailbox someone | Get-MailboxImportRequestStatistics -IncludeReport | select message



Error: This mailbox exceeded the maximum number of corrupt or missing items that were specified for this request.

Lastly, you can clean up the requests using this one. Feel free to filter by status as needed:

[PS] C:\>Get-MailboxImportRequest | where {$_.status -eq "Completed"} | Remove-MailboxImportRequest

#2013, #exchange, #import, #powershell, #pst

Comparer l’appartenance des groupes AD entre 2 comptes

Ce document décrit le script qui permet de comparer 2 comptes AD afin de calquer les appartenances.

exécution des scripts powershell
droit de modification de comptes AD

Le script

<pre>    $sourceacc, 
# Checks if both accounts are provided as an argument, otherwise prompts for input 
if (-not $sourceacc) { $sourceacc = read-host "Please input source user name, the user the rights will be read from" } 
if (-not $destacc) { $destacc = read-host "Please input destination user name, the user which will be added to the groups of the source user" } 
# Retrieves the group membership for both accounts 
$sourcemember = get-aduser -filter {samaccountname -eq $sourceacc} -property memberof | select memberof 
$destmember = get-aduser -filter {samaccountname -eq $destacc} -property memberof | select memberof 
# Checks if accounts have group membership, if no group membership is found for either account script will exit 
if ($sourcemember -eq $null) {"Source user not found";return} 
if ($destmember -eq $null) {"Destination user not found";return} 
# Checks for differences, if no differences are found script will prompt and exit 
if (-not (compare-object $destmember.memberof $sourcemember.memberof | where-object {$_.sideindicator -eq '=>'})) {write-host "No difference between $sourceacc & $destacc groupmembership found. $destacc will not be added to any additional groups.";return} 
# Routine that changes group membership and displays output to prompt 
compare-object $destmember.memberof $sourcemember.memberof | where-object {$_.sideindicator -eq '=>'} | 
    select -expand inputobject | foreach {write-host "$destacc will be added to:"([regex]::split($_,'^CN=|,OU=.+$'))[1]} 
# If no confirmation parameter is set no confirmation is required, otherwise script will prompt for confirmation 
if ($noconfirm)    { 
    compare-object $destmember.memberof $sourcemember.memberof | where-object {$_.sideindicator -eq '=>'} |  
        select -expand inputobject | foreach {add-adgroupmember "$_" $destacc} 
else { 
        $UserInput = Read-Host "Are you sure you wish to add $destacc to these groups?`n[Y]es, [N]o or e[X]it" 
        if (("Y","yes","n","no","X","exit") -notcontains $UserInput) { 
            $UserInput = $null 
            Write-Warning "Please input correct value" 
        if (("X","exit","N","no") -contains $UserInput) { 
            Write-Host "No changes made, exiting..." 
        if (("Y","yes") -contains $UserInput) { 
            compare-object $destmember.memberof $sourcemember.memberof | where-object {$_.sideindicator -eq '=>'} |  
                select -expand inputobject | foreach {add-adgroupmember "$_" $destacc} 
    until ($UserInput -ne $null) 

Utilisation du script

activer le module ActiveDirectory

Import-Module activedirectory

lancer le script

Please input source user name, the user the rights will be read from: user1
Please input destination user name, the user which will be added to the groups of the source user: user2
pruban will be added to: Group ABC 1
pruban will be added to: Group ABC 2
pruban will be added to: Group ABC 36dfa920
pruban will be added to: Group ABC 43
pruban will be added to: Group ABC 42
pruban will be added to: Group ABC 45
pruban will be added to: Group ABC 543
pruban will be added to: Group ABC 45
pruban will be added to: Group ABC 34

Are you sure you wish to add user2 to these groups?
[Y]es, [N]o or e[X]it: Y


#ad, #compare, #group, #membership, #powershell, #script

Downloading files with windows server core

I found out that the invoke-webrequest doesn’t work so well on windows core. The Internet Explorer einginre required by the ParseHtml is apparently not supported on Server Core editions of windows Servers. If you want to do web page parsing on Server Core, be sure to use the -UseBasicParsing as a parameter of the request.

This mode performs only limited parsing on the uri – images, input fields, links and raw html content.

So in order to download onto your internet connected windows Core Server, you can use

$url = ""


Invoke-WebRequest -uri $url -OutFile $destination 

more practically if I wanted to download a hotfix, I’d go

> iwr -Uri
/6/F365E995-0C73-48D6-B520-3FF323CA913A/Windows8.1-KB2979576-x64.msu -UseBasicParsing -OutFile .\Windows8.1-KB2979576-x64.msu

Also for some type of file you might want to use the Unblock-File powershell cmdlet.

#2012r2, #core, #download, #invoke-webrequest, #powershell, #windows-core