Posts Tagged ‘PowerShell’


A pastebin is a type of web application where users can store plain text. There are many implementations and versions of pastebin found around the web, this Script is designed to with Stikked https://github.com/claudehohl/Stikked (mainly because that’s what we’re using at work). All the Linux systems I administer have the following alias for Curl

alias pastebin='curl -k -d text="`cat -`" -d name="`whoami`@`hostname -f`" https://paste.work.net/api/create 2>/dev/null'

This allows me to quickly pipe the results of a command to pastebin where I can then send the link via Instant Message/Twitter for example. I really like this functionality and wanted to have the same ability when I’m working at my Windows PowerShell prompt. The end result is an Advanced Function that wraps Invoke-WebRequest (similar in concept to cURL). This function allowed me to add additional parameters for options available in the API http://paste.scratchbook.ch/api, with my favourite being “Language” which provides the option to syntax highlight code.

Here is a quick video demo of the function in action

Below is the v1.0 code and here is a link to the code on my GitHub page which will contain the latest version https://github.com/jfrmilner/PowerShell-Out-PasteBin/blob/master/Out-PasteBin.ps1

function Out-PasteBin {
	<#
	.SYNOPSIS
	A PowerShell function to output to a Stikked PasteBin. 
		It fully supports being used in the current pipeline or simply cat'ing a text file. 
		The URL for the Paste is copied to the Clipboard for ease of access.
	Its recommended that this function be added to your PowerShell Profile to guarantee availability
	PowerShell Profiles - http://technet.microsoft.com/en-us/library/bb613488(v=VS.85).aspx 
	.PARAMETER <inputPipeline>
	Inbound object that will be converted to String for uploading to Stikked PasteBin
	.PARAMETER <Language>
	Code Language. Default = "text"
	.PARAMETER <username>
	Username. Default = current Windows logged in Username.
	.PARAMETER <Private>
	None private Pastes will be publicly listed and will appear on recent lists etc. Default = True (Private)
	.PARAMETER <expireMinutes>
	Paste liftime in Minutes. Default = 30.
	.EXAMPLE
	Get-VM | Out-PasteBin
	.EXAMPLE
	Get-ChildItem | Out-PasteBin -expireMinutes 120
	.EXAMPLE
	cat Out-PasteBin.ps1 | Out-PasteBin -language PowerShell
	.NOTES
		Author: jfrmilner/John Milner
		Blog  : https://jfrmilner.wordpress.com 
		File Name: Out-PostBin.ps1
		Requires: Powershell V2
		Legal: This script is provided "AS IS" with no warranties or guarantees, and confers no rights. You may use, modify, reproduce, and distribute this script file in any way provided that you agree to give the original author credit.
		Version: v1.0 - 2015/06/18
	#>
    param(
	   	[parameter(ValueFromPipeline=$true, ValueFromRemainingArguments=$true)]
	    $inputPipeline
		,
		[ValidateSet("html5", "css", "javascript", "php", "python", "ruby", "lua", "bash", "erlang", `
		"go", "c", "cpp", "diff", "latex", "sql", "xml", "text", "0", "4cs", "6502acme", "6502kickass",`
		"6502tasm", "68000devpac", "abap", "actionscript", "actionscript3", "ada", "algol68", "apache",` 
		"applescript", "apt_sources", "asm", "asp", "autoconf", "autohotkey", "autoit", "avisynth", "awk",` 
		"bascomavr", "basic4gl", "bf", "bibtex", "blitzbasic", "bnf", "boo", "c_loadrunner", "c_mac", `
		"caddcl", "cadlisp", "cfdg", "cfm", "chaiscript", "cil", "clojure", "cmake", "cobol", "coffeescript",`
		"csharp", "cuesheet", "d", "dcs", "delphi", "div", "dos", "dot", "e", "ecmascript", "eiffel", "email", `
		"epc", "euphoria", "f1", "falcon", "fo", "fortran", "freebasic", "fsharp", "gambas", "gdb", "genero", `
		"genie", "gettext", "glsl", "gml", "gnuplot", "groovy", "gwbasic", "haskell", "hicest", "hq9plus", `
		"html4strict", "icon", "idl", "ini", "inno", "intercal", "io", "j", "java", "java5", "jquery", "klonec", `
		"klonecpp", "lb", "lisp", "llvm", "locobasic", "logtalk", "lolcode", "lotusformulas", "lotusscript", `
		"lscript", "lsl2", "m68k", "magiksf", "make", "mapbasic", "matlab", "mirc", "mmix", "modula2", "modula3",`
		"mpasm", "mxml", "mysql", "newlisp", "nsis", "oberon2", "objc", "objeck", "ocaml", "oobas", "oracle11", `
		"oracle8", "oxygene", "oz", "pascal", "pcre", "per", "perl", "perl6", "pf", "pic16", "pike", "pixelbender", `
		"pli", "plsql", "postgresql", "povray", "powerbuilder", "powershell", "proftpd", "progress", "prolog", `
		"properties", "providex", "purebasic", "q", "qbasic", "rails", "rebol", "reg", "robots", "rpmspec", `
		"rsplus", "sas", "scala", "scheme", "scilab", "sdlbasic", "smalltalk", "smarty", "systemverilog", "tcl",`
		"teraterm", "thinbasic", "tsql", "typoscript", "unicon", "uscript", "vala", "vb", "vbnet", "verilog", "vhdl",`
		"vim", "visualfoxpro", "visualprolog", "whitespace", "whois", "winbatch", "xbasic", "xorg_conf", "xpp", `
		"yaml", "z80", "zxbasic")]
		[string]$language = "text"
		,
		[string]$username = [Environment]::UserName
		,
		[bool]$private = $true
		,
		[int]$expireMinutes = 30
	    )
	begin {
		Add-Type -AssemblyName System.Web
		Add-Type -AssemblyName System.Windows.Forms
		[array]$text=@()
	}
    process {
		$text += $inputPipeline
	}
	end {
		#Create Paste string from input
		$string = [System.Web.HttpUtility]::UrlEncode($($text | Format-Table -AutoSize | Out-String))
		$Global:PSContent = @() 
		$Global:PSContent += "private=$([int]$private)&"
		$Global:PSContent += "lang=$($language)&"
		$Global:PSContent += "name=$($username)&"
		$Global:PSContent += "expire=$($expireMinutes)&"
		$Global:PSContent += "title=pipeline&"
		$Global:PSContent += "text=$($string)"
		try
		 {
			#Upload to Stikked PasteBin. Change the Uri to your hosted Stikked PasteBin.
			Invoke-WebRequest -Uri 'http://paste.scratchbook.ch/api/create' -Method Post -Body $PSContent -OutVariable response | Out-Null
			
			#Copy Stikked URL response to Clipboard
			Write-Host $('[copied to clipboard] Postbin URL: {0}' -f $response.Content) -ForegroundColor Green -BackgroundColor Blue
			[System.Windows.Forms.Clipboard]::SetText( $response.Content, 'UnicodeText' )
		 }
		catch [system.exception]
		 {
		  	"caught a system exception"
		 }
	}
}

Note for WordPress code copy
If you double click anywhere on the code, the entire code view is replaced with a pre-selected view from which users can copy with a simple Ctrl+C. Clicking anywhere else returns the view to the original state.

To make this function useful it really should be available for all your PowerShell sessions so I recommended that you add it to your PowerShell Profile, see the following link for details – http://technet.microsoft.com/en-us/library/bb613488(v=VS.85).aspx

Regard,

jfrmilner

Advertisements

I’ve been using IFTTT for a while now and its really changing the way I use online services. Just recently they added Channels for NewsBlur and Boxcar which allows for some really interesting Recipes, such as if there is a new blog post (NewsBlur) then let you know via a Push notification to your mobile device (Boxcar). This works great for the majority of my feeds but there are some that are just to busy and need to be filtered on certain keywords or RegEx matches before sending me Push notifications, as this is not currently possible (hoping it will one day) I set out to do it myself..

Thankfully NewsBlur provide a simple web based API which can be easily access with PowerShell.

First we must login to the API


$postParams = @{username='jfrmilner';password='new$blur'}
Invoke-WebRequest -Uri 'https://www.newsblur.com/api/login' -Method Post -Body $postParams -SessionVariable newsblur

In the above lines we pass the credentials to the NewsBlur endpoint and store the session in a variable $newsblur.


$stories = @()
foreach ($i in 1..20) {
$postParams = @{page="$i";read_filter="unread"}
$newsContent = Invoke-WebRequest -Uri 'https://www.newsblur.com/reader/feed/4340960' -Method Get -WebSession $newsblur -Body $postParams
$stories += $newsContent.Content | ConvertFrom-Json
}

In the above lines we create an array to store our stories and then collect 20 pages of results from NewsBlur that are unread. The ability to get only unread messages is one of the main reasons I collect the feeds from NewsBlur rather than directly from the sites RSS feed.

The above feed is actually for HotUKDeals. HotUKDeals allow you to customise your own RSS feeds which is nice so for this example I setup one for Gaming deals in the UK – http://www.hotukdeals.com/rss/jfrmilner. You will also notice the feed uses a numeric ID, you can obtain this with a GET /reader/feeds call but for quick ad-hoc use the NewsBlur web portal, click your feed and you will see the number in the address bar, for example:

NewsBlurWebPortalIDLookup


$myWatchList = $stories.stories | ? { $_.story_title -match "Humble|Uncharted 3" }

In the above line I use a simple RegEx to filter for stories that mention Humble or Uncharted 3.

NewsBlurResults1

In the above screenshot we can confirm this is working as expected; now I just need to Push this information to my phone.

Now we will just loop the results to the Send-BoxcarPush function I created my previous post.

foreach ($story in $myWatchList) {
Send-BoxcarPush -notificationTitle $story.story_title -notificationLongMessage $story.story_permalink -notificationSound cash
}

And to the notification sound of cash (the cash I’ll be saving) my phone lights up

NewsBlurResults2

#POST /reader/mark_feed_as_read
$postParams = @{feed_id=4340960}
$newsContent = Invoke-WebRequest -Uri 'https://www.newsblur.com/reader/mark_feed_as_read' -Method POST -WebSession $newsblur -Body $postParams

In the above lines we mark the feed as read to prevent getting duplicate notifications.

#POST /api/logout
#Logout the currently logged in user.
Invoke-WebRequest -Uri 'https://www.newsblur.com/api/logout' -Method POST -WebSession $newsblur

And finally we logout in the above lines.

Now all you need to do is sent this up as a scheduled task in Windows to run as frequently as you need.

As always thank you for reading and if you found this post useful please share and/or leave a comment.

Regards, jfrmilner


Not wanting to add to my already bursting email inbox and finding the need for a more immediate mobile notification of scripted events I find myself looking at https://boxcar.io and their end-user API .

The API provides an example on how to use the service with cURL so as before I will be converting this simple format to use the PowerShell Invoke-WebRequest cmdlet and wrap this into an advanced function.

Setup
•    Download the Boxcar iOS app
•    Get your Access Token and replace the value for the $user_credentials variable in the Param section of the function  – How to get the Token from the client app
•    Like all PowerShell functions you will need to run the code at least once and you can call it as many times as required for the session.

The best way to copy the code from a WordPress blog such as this one is to double click in the below window and then copy, this usually preserves the formatting.

function Send-BoxcarPush  {
<#
.SYNOPSIS
A function to send Boxcar Push messages.
.DESCRIPTION
An example of using PowerShell to send Universal Push Notification messages. Typically the target device is a mobile running iOS (https://boxcar.io/client) or Andriod.
.PARAMETER notificationTitle
Message Title/Subject. 140 Character Maximum.
.PARAMETER notificationLongMessage
Message body text. 1000 Character Maximum.
.PARAMETER notificationSound
Notification Sound played on receiving device. Default is ‘bird-1’, see the Available sounds list for options - https://boxcar.uservoice.com/knowledgebase/articles/306788-how-to-send-your-boxcar-account-a-notification
.PARAMETER hostURI
Default is usually fine.
.PARAMETER user_credentials
Boxcar Access Token. Its recommended you change this value in the Param section of this function else it will need to be specified each time. The access token is available from the general "Settings" screen of Boxcar Client app.
.EXAMPLE
Send-BoxcarPush -notificationTitle "Test Title" -notificationLongMessage "Body message text"
.NOTES
Author: John Milner
Blog  : https://jfrmilner.wordpress.com
File Name: Send-BoxcarPush.ps1
Author: jfrmilner
Email: jfrmilner@googlemail.com
Requires: Powershell v4 (May work on older versions but untested)
Legal: This script is provided "AS IS" with no warranties or guarantees, and confers no rights. You may use, modify, reproduce, and distribute this script file in any way provided that you agree to give the original author credit.
Version: v1.0 - 2014 March 1st - First Version
Version: v1.1 - 2014 March 2nd - Added URL Encoding
.LINK
https://jfrmilner.wordpress.com/2014/03/01/powershell-pus…ered-by-boxcar/
#>
Param(
[String]
[parameter(Mandatory=$true)]
[ValidateLength(1,140)]
$notificationTitle = "test"
,
[String]
[parameter(Mandatory=$true)]
[ValidateLength(1,1000)] #Max is 4kb so 1000 is playing safe.
$notificationLongMessage = "text here"
,
[String]
$notificationSound = 'bird-1'
,
[String]
$hostURI = 'https://new.boxcar.io/api/notifications' #HOST: This is the server to use to send HTTPS API calls.
,
[String]
$user_credentials = 'Put Your Access Token Here' #Access Token. Change this value to your own here or specify here.
)

BEGIN{
Add-Type -AssemblyName System.Web
}#begin
PROCESS{

try
{
$message = Invoke-WebRequest -Uri $hostURI -Method POST -Body "user_credentials=$($user_credentials)&notification[title]=$([System.Web.HttpUtility]::UrlEncode($notificationTitle))&notification[long_message]=$([System.Web.HttpUtility]::UrlEncode($notificationLongMessage))&notification[sound]=$($notificationSound)"
if ($message.StatusCode -eq 201) {
"Message Sent:"
$message.Content | ConvertFrom-Json
}
}

catch [System.Net.WebException]
{
Write-Host  -ForegroundColor Red $_.Exception.Message
switch -regex ($_.Exception.Message)
{
"401" {Write-Host  -ForegroundColor Red "Failure: Check Access Token"}
"404" {Write-Host  -ForegroundColor Red "Failure: No associated device"}
"422" {Write-Host  -ForegroundColor Red "Failure: Unprocessable Entity - All non UTF-8 encoding are rejected"}
default {Write-Host  -ForegroundColor Red $_.Exception.Message}
}

}

finally
{

}
}#process
END{}#end

}

Example

BoxcarPowerShellExample

BoxCariOSExample

Boxcar deliver a great service which is free for 200 pushes per minute and its fast, very fast.
Boxcar notifications can use 20+ different sounds which I’m starting to find quite useful, for example you can have different scripts use different sounds which I just listen out for and this saves me having to look at the phone. I’m also thinking I could use different sounds for different levels of importance.

As always thank you for reading and if you found this post useful please share and/or leave a comment.

Regards, jfrmilner


At work this week I was working with one of our Linux engineers and he used the command “cURL ifconfig.me” which promptly provided him with the external IP of the system. I thought to myself, “I wonder if this is possible from PowerShell” and sure enough it is.

Using the Invoke-WebRequest cmdlet we can send http(s) requests to a web service. Note that this is a PowerShell 3.0 only cmdlet.

Let’s give it a try:

Invoke-WebRequest ifconfig.me

Get-ExternalIP-01

Not quite what we’re looking for. On inspection of the website http://ifconfig.me/ we can see that there are many command line options, lets try just the /IP option

Invoke-WebRequest ifconfig.me/ip

Get-ExternalIP-02

Much better and we can clearly see that the IP address is returned in Content which it fine for a quick check at the command line but not so great if your using it for scripting. To collect just the external IP we could wrap the command in parentheses, for example:

(Invoke-WebRequest ifconfig.me/ip).Content

Nice.

I thought that this would make a handy addition to my PowerShell Profile so I created a quick function


function Get-ExternalIP {
(Invoke-WebRequest ifconfig.me/ip).Content
}

Get-ExternalIP-03

Perfect!

An alternative to ifconfig.me is available at http://www.whatismyip.com/ip-faq/automation-rules/, for example

(Invoke-WebRequest http://automation.whatismyip.com/n09230945.asp).Content

Thanks for reading

Regards,

jfrmilner


This month we have a new Avaya phone system being implemented for one of my customers and one of the prerequisites to get full functionality is to have all telephone numbers in the popular E.164 format. This would be a pretty simple task if the data was in a consistent format but unfortunately this was not the case, let’s take a look at the state of the data*:

*For security reasons I have recreated the data in my test lab with random names, area codes and telephone numbers etc.

First I will store a collection of Users in a variable named $users, throughout this post I will use the Quest AD cmdlets. This is mainly because they are backwards compatible with 2003 based domains but if you are lucky enough to be 2008 you should be able to convert the commands to Microsoft’s AD Module with little effort.

$users = Get-QADUser -Description "E164 Sample Accounts"

And now display just the name and telephoneNumber properties:

$users | Select-Object Name, telephoneNumber | Format-Table -AutoSize
Name              telephoneNumber
----              ---------------
Georgina.Stewart  554861
Georgina.Stewart1 01234 12 34 56 (only call on Tues)
Eden.Morris       01234-55(4487)
Sarah.Patel       559642
Marley.Adams      550998
Mya.Lewis         (01234) 127075
Sophia.Baker      (55)2912
Filip.Rogers      (55)8996
Noah.Mason        (Part Time)-(01234) 897740
Isabel.Webb       (01234) 036428
Steven.Matthews   (01234) 957823
Courtney.Walker   (01234) 679860
Rayyan.Mitchell   07123 530656
Mary.Graham       07123  822057
Maisy.Barnes      07123 364461
Arabella.Thompson +441234844048
Macy.Adams
Grace.Robinson    (01234) 538794
Summer.Stevens    Call me before 11AM 550997
Frederick.Taylor  01234588239

As you can see we have quite a mix bag here. One of the first things to notice is that the telephoneNumber property has not been strictly used for just numbers and as such it is difficult to see any format patterns, let’s strip all non-digit characters away and take another look:

$users | Select-Object Name, @{name="telephoneNumberDigitsOnly";expression={$_.telephoneNumber -replace "\D"}} | Format-Table –AutoSize

The above line uses a replace with a single argument so this is effectively a remove. The replace uses a regular expression of Non-Digit contained within a calculated property.

Name              telephoneNumberDigitsOnly
----              -------------------------
Georgina.Stewart  554861
Georgina.Stewart1 01234123456
Eden.Morris       01234554487
Sarah.Patel       559642
Marley.Adams      550998
Mya.Lewis         01234127075
Sophia.Baker      552912
Filip.Rogers      558996
Noah.Mason        01234897740
Isabel.Webb       01234036428
Steven.Matthews   01234957823
Courtney.Walker   01234679860
Rayyan.Mitchell   07123530656
Mary.Graham       07123822057
Maisy.Barnes      07123364461
Arabella.Thompson 441234844048
Macy.Adams
Grace.Robinson    01234538794
Summer.Stevens    11550997
Frederick.Taylor  01234588239

Now that looks better with the exception of Summer.Stevens, it’s expected that some accounts will need manual effort so I will need to filter for those as well. We can see from the above table that there are two common number formats (1,2), blanks (3), numbers already in E.164 format and finally we’ll need a catch all(5) for everything else:

  1. Starting with “55” followed by four numbers
  2. Starting with “01 or 07” followed by nine numbers
  3. Blank/Null values
  4. E.164 formatted numbers (No change required)
  5. Neither of the above formats, for example Summer.Stevens

The next step is to create regular expressions to match these four values as the fifth will be a catch for non-matched values, like so:

  1. “^(01|07)\d{9}”
  2. “^55\d{4}”
  3. “^$”
  4. “^44\d{10}”

Now let’s put all of this together and get a feel for how things will look:

foreach ($user in $users) {
	switch -regex ($user | % { $user.telephoneNumber -replace "\D" } ) {
	"^(01|07)\d{9}"		{ Add-Member -InputObject $user -Name "E164Number" -MemberType NoteProperty -Value ([regex]::matches(($user.telephoneNumber -replace "\D"),"^(01|07)\d{9}")[0].Value -replace '^0','+44') ; continue }
	"^55\d{4}"			{ Add-Member -InputObject $user -Name "E164Number" -MemberType NoteProperty -Value ([regex]::matches(($user.telephoneNumber -replace "\D"),"^55\d{4}")[0].Value -replace '^55','+44123455') ; continue }
	"^$"				{ Write-Warning "Blank" ; continue } #Empty
	"^44\d{10}"			{ if ($user.telephoneNumber -match "^\+\d{12}") { Write-Warning "E.164: $($user.sAMAccountName), $($user.telephoneNumber)" } else { Write-Warning "No Match: $($user.sAMAccountName), `"$($user.telephoneNumber)`""} ; continue}
	default 			{ Write-Warning "No Match: $($user.sAMAccountName), `"$($user.telephoneNumber)`"" }
	}
}

As you can see from the above script block I have used a foreach loop that then uses a switch statement. The switch then checks to see if it can match one of the regular expressions patterns from the previous step and if successful adds a new note property named “E164Number” else it will display a warning. Let’s give it a try:

WARNING: E.164: Arabella.Thompson, +441234844048
WARNING: Blank
WARNING: No Match: Summer.Stevens, "Call me before 11AM 550997"

As predicted we have a few warnings, now let’s have a look at the projected telephone numbers:

$users | Select-Object Name, telephoneNumber, E164Number | Format-Table -AutoSize
Name              telephoneNumber                     E164Number
----              ---------------                     ----------
Georgina.Stewart  554861                              +441234554861
Georgina.Stewart1 01234 12 34 56 (only call on Tues)  +441234123456
Eden.Morris       01234-55(4487)                      +441234554487
Sarah.Patel       559642                              +441234559642
Marley.Adams      550998                              +441234550998
Mya.Lewis         (01234) 127075                      +441234127075
Sophia.Baker      (55)2912                            +441234552912
Filip.Rogers      (55)8996                            +441234558996
Noah.Mason        (Part Time)-(01234) 897740          +441234897740
Isabel.Webb       (01234) 036428                      +441234036428
Steven.Matthews   (01234) 957823                      +441234957823
Courtney.Walker   (01234) 679860                      +441234679860
Rayyan.Mitchell   07123 530656                        +447123530656
Mary.Graham       07123  822057                       +447123822057
Maisy.Barnes      07123 364461                        +447123364461
Arabella.Thompson +441234844048
Macy.Adams
Grace.Robinson    (01234) 538794                      +441234538794
Summer.Stevens    Call me before 11AM 550997
Frederick.Taylor  01234588239                         +441234588239

Very nice. Now to make this permanent we need to push the E164Number property into the telephoneNumber property on each of these users:

Backup first:

$users | Export-Csv C:\Support\MyBackup.csv -NoTypeInformation

Once the blackup task has been completed we can now go ahead and apply the new number. Notice that I used an if statement to check that there actually is a value in the E164Number property first and if there isn’t then it will skip that user.

 $users | ForEach-Object { if ($_.E164Number) {Set-QADUser -Identity $_ -PhoneNumber $_.E164Number}} 

Now to confirm the results:

$users | Get-QADuser | Select-Object Name, telephoneNumber | Format-Table -AutoSize
Name              telephoneNumber
----              ---------------
Georgina.Stewart  +441234554861
Georgina.Stewart1 +441234123456
Eden.Morris       +441234554487
Sarah.Patel       +441234559642
Marley.Adams      +441234550998
Mya.Lewis         +441234127075
Sophia.Baker      +441234552912
Filip.Rogers      +441234558996
Noah.Mason        +441234897740
Isabel.Webb       +441234036428
Steven.Matthews   +441234957823
Courtney.Walker   +441234679860
Rayyan.Mitchell   +447123530656
Mary.Graham       +447123822057
Maisy.Barnes      +447123364461
Arabella.Thompson +441234844048
Macy.Adams
Grace.Robinson    +441234538794
Summer.Stevens    Call me before 11AM 550997
Frederick.Taylor  +441234588239

Excellent, the majority of users now have an E164 formatted phone number.

All that’s left now is to identify users that still do not conform to this format so that they can be contacted, this can be achieved by using a Where-Object with another regular expression like so:

$users | Get-QADUser | Where-Object { $_.telephoneNumber -notmatch "^\+\d{12}" } | Select-Object name, email
Name           Email
----           -----
Macy.Adams     Macy.Adams@jfrmilner.lab
Summer.Stevens Summer.Stevens@jfrmilner.lab

Well that wraps up this post; I think you’ll agree it’s a nice real world example of using PowerShell with regular expressions to solve formatting issues.

Thanks for reading and until next time.

Regards,

jfrmilner


Part 3 of 3

First Post (Part 1 of 3)

Previous Post (Part 2 of 3)

Step 7 – Force a restart of the Servers

I now need to be sure that the servers are online. A quick way to test that a bunch of systems are online after a reboot it to use the Test-Connection cmdlet, for example to single ping the first ten servers that I demoted:

Test-Connection $Servers[0..9] -Count 1

After confirming the servers were back online I restarted them again, this oddly was necessary to freshen up the systems as they seemed a little flaky on the first restart after a demotion. Instead of using the Restart-Computer cmdlet I used the old shutdown.exe command as I found it more reliable dealing with unhappy servers, the command I used was:

$Servers[0..9] | % {  shutdown /r /m \\$($_) }

After the second restart I tested that PowerShell remoting with CredSSP authentication was again working using the same command as before:

icm $Servers { $ENV:ComputerName } -Authentication CredSSP -Credential $Cred

Step 8 – ReDCPROMO to RODC

Completing all the previous steps I was now ready to promote all the member servers to RODCs.

Much like the command used for step 6 here is the command I used to promote the first 10 servers.

icm $Servers[0..9] { dcpromo.exe /unattend:C:\SUPPORT\DCPROMORODCAnswerFile.txt | Tee-Object -filepath C:\SUPPORT\DCPROMORODCResultFile.txt } -Authentication CredSSP -Credential $Cred

Again the output for this command is both seen on the console and saved to the file C:\SUPPORT\DCPROMORODCResultFile.txt local to each server. Due to the IFM cache of AD the whole promotion completed very quickly, with some completing in only a few minutes.

You should now find the RODCs returned to the ‘Domain Controllers’ OU but now the DC Type will show Read-only.

Step 9 – Replicate the Passwords for the User and Computer Objects to the local RODC responsible for authentication

Now that all the DCs had been converted to RODCs I wanted to be sure that I pre-cached all the computer accounts and user account passwords local to that site using the groups created in Step 1. The command line tool RepAdmin can be used for such a task with the /rodcpwdrepl switch, this will first check the computer/user object is allowed to be cached and if confirmed will then add the password hash to the RODCs cache. Interestingly you can only add single user or computer account to the RODC cache using ADUC and not groups or as I needed all accounts contained within an OU.

I achieved this using the help of the Microsoft AD module. As mentioned before the AD Site Name in this company also matches up with the name of the OU holding the objects for that AD Site, so with that in mind I checked what the local site code was and then used that to construct the Distinguished Name of the OU, I then enumerated all users/computers and passed this onto the RepAdmin tool.

Here is the example of populating the RODC cache with User accounts:

icm $Servers[0..9] { import-module ActiveDirectory ; $siteCode = [DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite().name  ; Get-ADUser -SearchBase "OU=Staff,OU=Users,OU=$($siteCode),OU=Schools,DC=domain1,DC=sch,DC=uk" -filter * | % { Repadmin /rodcpwdrepl D1-$($siteCode)-001 D1-DC-001 $_.DistinguishedName }} -Authentication CredSSP -Credential $Cred

And finally here is the example of populating the RODC cache with Computer Accounts:

icm $Servers[0..9] { import-module ActiveDirectory ; $siteCode = [DirectoryServices.ActiveDirectory.ActiveDirectorySite]::GetComputerSite().name  ; Get-ADComputer -SearchBase "OU=$($siteCode),OU=Schools,DC=domain1,DC=sch,DC=uk" -filter * | % { Repadmin /rodcpwdrepl D1-$($siteCode)-001 D1-DC-001 $_.DistinguishedName }} -Authentication CredSSP -Credential $Cred

You can easily confirm which accounts are cached on your server by selecting the properties on the RODC, selecting the “Password Replication Policy” tab, clicking the Advanced button and confirming that “Accounts Whose Password are stored on this Read-only Domain Controller” is selected from the drop down box.

I used this process to successfully convert over 150 full DCs to RODCs and I found it an excellent example of how PowerShell remoting can save you significant amounts of time and making a repetitive task like this a breeze!

A couple of final thoughts:

1. Be sure to monitor your replication over the coming days and get familiar with the repadmin command line utility.

2. It would be wise to remove the IFM caches from your local disks as soon as you are ready and be sure not to use them after the tombstone lifetime.

Thanks for reading.

Regards,

jfrmilner

This post is provided “AS IS” with no warranties or guarantees, and confers no rights.

[/sourcecode] 


Part 2 of 3

Previous Post (Part 1 of 3)

Final Post (Part 3 of 3)

Step 4 – Enable CredSSP (multihop-authentication)

Before I could start using DCPROMO I needed to enable multihop-authentication using CredSSP. Now I will not explain this in detail, instead I would like to refer you to the excellent post by Ravikanth Chaganti – http://www.ravichaganti.com/blog/?p=1230

As this will need to be allowed in bulk then I suggest you use the GPO :
Computer Configuration/Administrative Templates/Windows Components/Windows Remote Management (WinRM)/WinRM Service/Allow CredSSP authentication

This policy setting allows you to manage whether the Windows Remote Management (WinRM) service accepts CredSSP authentication from a remote client. Once enabled this policy sets the WinRM service to accept CredSSP authentication from a remote client.

The management client will also need to be configured to allow credentials to be passed onto remote clients. In my case I only wanted to allow this on my management server so I used this command on that system:


Enable-WSManCredSSP -Role Client -DelegateComputer *.domain1.sch.uk

This command once enabled basically allows me to pass my credentials onto any system in the domain that has the WinRM service configured to accept CredSSP authentication.

I recommend that you give this a test run before moving onto the next step.
Create a variable $Cred and store the account you’re going to be using for the DCPROMO commands, for example:


$Cred = Get-Credential DOMAIN1\DomainAdmin

Then use Invoke-Command to echo back the server name(s) of each remote system:

icm $Servers { $ENV:ComputerName } -Authentication CredSSP -Credential $Cred

Step 5 – Create DCPROMO demote and RODC promote answer files using a word replace template method

The idea of this is quite simple, first create a DCPROMO demote and a RODC promotion answer files from a test/lab server and use these as templates. The template will be loaded into RAM and if necessary a word replace will be performed creating the unique answer file needed, the file will then be saved onto the target server. This was the code used to create the two answer files:


$SiteCodes| % {
$DCPROMODemote = gc 'C:\Scripts\DCPROMODemoteTemplate.txt'
$DCPROMODemote | Out-File "\\D1-$($_)-001\C$\Support\DCPROMODemote.txt" -Encoding ascii
Write-Host "File Saved: \\D1-$($_)-001\C$\Support\DCPROMODemote.txt"
$DCPROMORODCTemplate = gc 'C:\Scripts\DCPROMO-RODC-Template.txt'
$DCPROMORODCAnswerFile = $DCPROMORODCTemplate -replace '',$($_)
$DCPROMORODCAnswerFile | Out-File "\\D1-$($_)-001\C$\Support\DCPROMORODCAnswerFile.txt" -Encoding ascii
Write-Host "File Saved: \\D1-$($_)-001\C$\Support\DCPROMORODCAnswerFile.txt"
}

Next the answer file used for the DCPROMO demotion, no word replacement was necessary.


; DCPROMO unattend file - Auth jfrmilner
; Usage:
;   dcpromo.exe /unattend:C:\SUPPORT\DCPROMODemote.txt
;
[DCInstall]
; Demotion
RetainDcMetadata=No
IsLastDCInDomain=No
AdministratorPassword=passwordgoeshere
RebootOnCompletion=Yes

This was the template answer file used for the DCPROMO RODC promotion. All text that matched ‘<SiteCode>’ was replaced with the Site Code passed from the pipeline, for example if the first value in the $SiteCode variable was ‘AFCPS’ then the line ‘PasswordReplicationAllowed=”DOMAIN1\<SiteCode>-Laptops”’ would be changed to ‘PasswordReplicationAllowed=”DOMAIN1\AFCPS-Laptops”’.


; DCPROMO unattend file - Auth jfrmilner
; Usage:
;   dcpromo.exe /unattend:C:\Support\DCPROMORODCAnswerFile.txt
;
[DCInstall]
; Read-Only Replica DC promotion
ReplicaOrNewDomain=ReadOnlyReplica
ReplicaDomainDNSName=domain1.sch.uk
; RODC Password Replication Policy
PasswordReplicationDenied="BUILTIN\Administrators"
PasswordReplicationDenied="BUILTIN\Server Operators"
PasswordReplicationDenied="BUILTIN\Backup Operators"
PasswordReplicationDenied="BUILTIN\Account Operators"
PasswordReplicationDenied="DOMAIN1\Denied RODC Password Replication Group"
PasswordReplicationAllowed="DOMAIN1\Allowed RODC Password Replication Group"
PasswordReplicationAllowed="DOMAIN1\<SiteCode>-Laptops"
PasswordReplicationAllowed="DOMAIN1\<SiteCode>-Pupils"
PasswordReplicationAllowed="DOMAIN1\<SiteCode>-Staff"
PasswordReplicationAllowed="DOMAIN1\<SiteCode>-Workstations"
; D1-RODC-Admins will include the Service Desk Global Group
DelegatedAdmin="DOMAIN1\D1-RODC-Admins"
SiteName=<SiteCode>
InstallDNS=Yes
ConfirmGc=Yes
CreateDNSDelegation=No
UserDomain=domain1.sch.uk
UserName=DOMAIN1\DomainAdmin
Password= passwordgoeshere
ReplicationSourcePath="C:\Support\IFM_RODC"
ReplicationSourceDC=D1-DC-001.domain1.sch.uk
DatabasePath="C:\Windows\NTDS"
LogPath="C:\Windows\NTDS"
SYSVOLPath="C:\Windows\SYSVOL"
SafeModeAdminPassword= passwordgoeshere
; Run-time flags (optional)
; CriticalReplicationOnly=Yes
RebootOnCompletion=Yes

I should point out that the passwords in these files are stored in clear text when they are created. The passwords are removed from each file after they have been used as answer files for the DCPROMO tasks, with this in mind you should consider carefully which account you use and not to create these files until you are ready to use them.

Step 6 – DCPROMO demote the DC to a Member Server

Before I began to demote servers I created a new OU titled ‘DCPROMO Holding’, this OU also had a GPO linked that would enable PowerShell remoting. By default a demoted server would be returned to the Computer OU and because of this would have all GPO’s removed.

With all the prerequisites out of the way I could now start demoting Domain Controllers back to Member Servers.
I did about ten servers at a time and I suggest you do the same. I did this by using the Range operator, for example say $Servers was a 100 item array, writing $Servers[0..9] would select the first 10 and $Servers[10..19] would be the next ten etc..

This is the code I used for the first 10:


icm $Servers[0..9] { dcpromo.exe /unattend:C:\SUPPORT\DCPROMODEMOTE.txt | Tee-Object -filepath C:\SUPPORT\DCPROMODEMOTEResultFile.txt } -Authentication CredSSP -Credential $Cred

Note the Tee-Object cmdlet, this allows the output of the promotion to appear on the console and also a text file. It would not be difficult to parse the text file for detailed information or collect the data and concatenate this information into a single report.

All went to plan, the computer objects for all ten systems were returned to the Computers OU. Checking this OU is actually the quickest way to get an overall feel of the success of the bulk demotion. I then moved the computer objects from the Computers OU to the ‘DCPROMO Holding’ OU.

Thanks for reading. I will post the final part over the course of the week so please check back.

Regards,

jfrmilner

; DCPROMO unattend file – Auth jfrmilner

; Usage:

;   dcpromo.exe /unattend:C:\Support\DCPROMORODCAnswerFile.txt

;

[DCInstall]

; Read-Only Replica DC promotion

ReplicaOrNewDomain=ReadOnlyReplica

ReplicaDomainDNSName=domain1.sch.uk

; RODC Password Replication Policy

PasswordReplicationDenied=”BUILTIN\Administrators”

PasswordReplicationDenied=”BUILTIN\Server Operators”

PasswordReplicationDenied=”BUILTIN\Backup Operators”

PasswordReplicationDenied=”BUILTIN\Account Operators”

PasswordReplicationDenied=”DOMAIN1\Denied RODC Password Replication Group”

PasswordReplicationAllowed=”DOMAIN1\Allowed RODC Password Replication Group”

PasswordReplicationAllowed=”DOMAIN1\<SiteCode>-Laptops”

PasswordReplicationAllowed=”DOMAIN1\<SiteCode>-Pupils”

PasswordReplicationAllowed=”DOMAIN1\<SiteCode>-Staff”

PasswordReplicationAllowed=”DOMAIN1\<SiteCode>-Workstations”

; D1-RODC-Admins will include the Service Desk Global Group

DelegatedAdmin=”DOMAIN1\D1-RODC-Admins”

SiteName=<SiteCode>

InstallDNS=Yes

ConfirmGc=Yes

CreateDNSDelegation=No

UserDomain=domain1.sch.uk

UserName=DOMAIN1\DomainAdmin

Password= passwordgoeshere

ReplicationSourcePath=”C:\Support\IFM_RODC”

ReplicationSourceDC=D1-DC-001.domain1.sch.uk

DatabasePath=”C:\Windows\NTDS”

LogPath=”C:\Windows\NTDS”

SYSVOLPath=”C:\Windows\SYSVOL”

SafeModeAdminPassword= passwordgoeshere

; Run-time flags (optional)

; CriticalReplicationOnly=Yes

RebootOnCompletion=Yes


Part 1 of 3

Next Post (Part 2 of 3)

Final Post (Part 3 of 3)

Scenario: This month presented me with an interesting issue that I would like to share. In effort to provide Role Based Accounts (RBA) to both 1st and 2nd Line Support it became clear that 2nd Line needed to locally administer DCs as they also acted as File Servers at some sites, but being a member of Domain Admins was overkill and as such went against Least Privilege best practices. The solution to this issue was to convert all non-dedicated DCs or FSMO Role holders to Read Only Domain Controllers (RODCs). RODCs run as member servers with a local Security Accounts Manager (SAM) and run AD as an isolated service; this allowed me to configure dedicated local administration. This was achieved by adding the 2nd Line role based account group to the BUILTIN\Administrators group on each local RODC. There are other benefits to RODCs but I’ll not cover those in this post.

Now the issue is that this particular Domain has over 150 DCs that need to be converted to RODCs with the majority being on slow WAN links. I needed to find a way to do this with minimum network traffic, in a consistent manor (to keep the Change Manager happy) and quickly. This was how I did it:

Summary of steps to perform:
1. Sort all Computer and User Objects into Groups that will be used for Password Replication Policies.
2. Set the DNS Server options on each DC NIC to point back to the Core DCs in Head Office.
3. Use the “Install From Media” option of NTDSUTIL to create two local cache copies of the Active Directory. One will be for RODCs and the other will be for Full DCs, the latter could be used for Roll Back scenarios should I wish to restore the DC to its original state.
4. Enable CredSSP used for multihop authentication.
5. Create DCPROMO demote and RODC promote answer files using a word replace template method.
6. DCPROMO demote the DC to a Member Server.
7. Force a restart of the Servers.
8. ReDCPROMO to RODC.
9. Replicate the Passwords for the User and Computer Objects to the local RODC responsible for authentication.

Step 1 – Sort all Computer and User Objects into Groups that will be used for Password Replication Policies

Luckily this step was particularly straight forward for me due to the fact that we use a five letter code for each customer and this code is used to reference the Site, Parent OU and is also used to prefix the Computer Objects. All I needed to do was create an array of all the Site Codes and store it in a variable called $siteCode, then pass that through to a foreach loop that collected all the Computer Objects for that OU and then added them to a Group following a simple pattern match.

This is the code that I used for the first batch of ten (Please note that I used the free Quest AD cmdlets for this and some of the other steps and as such they will need to be installed for this to work):


$siteCodes[0..9] | % {
$siteCode = $($_)
#Sort Computer Membership
$Comps = Get-QADComputer -OU domain1.sch.uk/Schools/$siteCode | ? { $_.memberof.count -ne 1 }
$Comps | ? { $_.Name -match "$($siteCode)W" } | % { Add-QADGroupMember -Identity "$($siteCode)-Workstations" -Member $_.DN }
$Comps | ? { $_.Name -match "$($siteCode)L" } | % { Add-QADGroupMember -Identity "$($siteCode)-Laptops" -Member $_.DN }
}

I also used the below code to list any Computer Objects that were not a member of a single Group, which for me mainly highlighted incorrectly named systems.


$siteCodes[0..9] | % {
$siteCode = $($_)
#Check for Problem Systems
Get-QADComputer -OU domain1.sch.uk/Schools/$siteCode | select name,@{name='MemberShipCount';expression={$_.memberof.count}} | ? { $_.Membershipcount -ne 1 }
}

Step 2 – Set the DNS Server options on each DC NIC to point back to the Full DCs in Head Office.

If you’re going to DCPROMO demote a Server from a DC to a Member Server you will need to be sure that the Server does not reference itself as a DNS Server. I needed to find the teamed NIC that is configured with the DNS registered IP Address of that Server and reconfigure the DNS Search Order. I did this by using PowerShell Remoting to first collect the IP Address from DNS that references the Servers host name and then that information to find the NIC with that configured, and then use a WMI method to set this NICs DNS Search Order to an Array using a couple of DNS Server from Head Office.


#Set DNS
icm $Servers -ScriptBlock {  $DNSArray = '10.10.10.10','10.10.10.11' ; (Get-WmiObject -class win32_networkadapterconfiguration | ? { $_.IPAddress -eq ( ([System.Net.Dns]::GetHostEntry($ENV:ComputerName).AddressList | ? { $_.AddressFamily -eq 'InterNetwork' }).IPAddressToString ) } ).SetDNSServerSearchOrder($DNSArray) }

To Check my configuration Changes:


#Audit DNS
icm $Servers -ScriptBlock { Get-WmiObject -class win32_networkadapterconfiguration | ? { $_.IPAddress -eq ( ([System.Net.Dns]::GetHostEntry($ENV:ComputerName).AddressList | ? { $_.AddressFamily -eq 'InterNetwork' }).IPAddressToString )} } | select __SERVER,IPAddress,DNSServerSearchOrder

Step 3 – Use the “Install From Media” (IFM) option of NTDSUTIL to create two local cache copies of the Active Directory.
One will be for RODCs and the other will be for Full DCs, the latter could be used for Roll Back scenarios should I wish to restore the DC to its original state.

To begin with I needed to increase the amount of memory that a PowerShell remoting session can use from the default 150MB to 1GB due to the memory hungry IFM process. This setting can be restored as soon as you have finished creating your IFM snapshots. Using the Group Policy Management Console (GPMC) I edited the policy that manages Remoting and edited the setting Computer Configuration/Administrative Templates/Windows Components/Windows Remote Shell/ Specify maximum amount of memory in MB per Shell, this option should look like Figure1.

RODC-GPOMaxMemPerShellMB

RODC-GPOMaxMemPerShellMB

The next step is to create the actual IFM snapshots using NTDSUTIL. This again was achieved using PowerShell Remoting; here is the code I used:


icm $Servers -ScriptBlock { ntdsutil "Activate Instance NTDS" ifm "Create SYSVOL Full C:\Support\IFM_Full" q q >> C:\Support\IFM_Full.log }
icm $Servers -ScriptBlock { ntdsutil "Activate Instance NTDS" ifm "Create SYSVOL RODC C:\Support\IFM_RODC" q q >> C:\Support\IFM_RODC.log }

Thanks for reading. I will post the remaining two parts over the course of the week so please check back.

Regards,

jfrmilner


The objective of this post is to provide instruction on enabling PowerShell 2.0 remoting capabilities in non 2008 R2 domains. In this example I will demonstrate in a 2003 domain environment.

Prerequisites

To complete this work instruction you will need the following:

1.     PowerShell version 2.0 will need to be installed on all systems that will be used for remoting. On down level clients PowerShell 2.0 is bundled into the Windows Management Framework.

The Windows Management Framework makes some updated management functionality of Windows 7 and Windows Server 2008 R2 available on Windows XP, Windows Server 2003, Windows Vista, and Windows Server 2008.

Windows Management Framework contains the following three components:

  • Windows Remote Management (WinRM) 2.0
  • Windows PowerShell 2.0
  • Background Intelligent Transfer Service (BITS) 4.0.

I recommend that WSUS is used as the deployment method for the Windows Management Framework on down level clients and it will be assumed from this point that this is already installed as this was released some time ago.

2.     The ‘Windowsremotemanagement.adm’ file will need to be imported into the GPO later in the process. This will need to be downloaded as part of the ‘WS-Management v1.1 Package’ at the following link http://support.microsoft.com/kb/936059 . Once downloaded install onto the system you will be using to create and edit GPO’s.

Create the Group Policy Object (GPO)

Launch the Group Policy Management (GPMC) and create a new GPO titled ‘Windows Remote Management’.

Edit the newly created GPO and add both the ‘Windowsremoteshell.adm’ and ‘Windowsremotemanagement.adm’ Templates, if either of the adm files cannot be found please ensure that the ‘WS-Management Package’ is downloaded and installed as advised in the prerequisites.

Now Expand trough the Computer Configuration policy structure to ‘Administrative Templates > Windows Components > Windows Remote Management (WinRM) > WinRM Service’ and select ‘Allow automatic configuration of listeners’

This policy setting allows you to manage whether the Windows Remote Management (WinRM) service automatically starts and listens on the network for requests on HTTP over port 5985 and if enabled HTTPS on 5986.

Note: The above ports are correct as of WinRM 2.0. The previous version WinRM 1.1 used the default HTTP and HTTPS ports 80 and 443 respectively, the documentation on TechNet and also on the help section for this GPO have not been updated to reflect this at the time of writing.

Enable the GPO and complete the IPv* filters text boxes, an example of a relaxed configuration can be seen in Figure 1

Figure 1

The service listens on the addresses specified by the IPv4 and IPv6 filters. IPv4 filter specifies one or more ranges of IPv4 addresses and IPv6 filter specifies one or more ranges of IPv6addresses. If specified, the service enumerates the available IP addresses on the computer and uses only addresses that fall within one of the filter ranges.

You can use asterisk (*) to indicate that the service listens on all available IP addresses on the computer. When * is used, other ranges in the filter are ignored. If the filter is left blank, the service does not listen on any addresses.

For example, if you want the service to listen only on IPv4 addresses, leave the IPv6 filter empty.

Ranges are specified using the syntax IP1-IP2. Multiple ranges are separated using ‘,’ (comma) as the delimiter.

Example IPv4 filters:

2.0.0.1-2.0.0.20, 24.0.0.1-24.0.0.22

Example IPv6 filters:

3FFE:FFFF:7654:FEDA:1245:BA98:0000:0000-3FFE:FFFF:7654:FEDA:1245:BA98:3210:4562.

Confirm WinRM Listener

Assuming the GPO in now Enabled and Linked to an OU containing the computers targeted for remoting, log onto one of the client machines in the domain, run “gpupdate /force” or wait for the group policy to be deployed to the client machine.

To enumerate the currently applied GPO use the “gpresult” command and confirm that the GPO titled ‘Windows Remote Management’ is listed in the ‘Applied Group Policy Objects’ section.

To enumerate the WinRM listener(s) and confirm the GPO configuration successfully applied use the following command:

winrm e winrm/config/listener

A new GPO source listener should have been created automatically and the output should resemble Figure 2

Figure 2

Open up a PowerShell console on a system connected to the same domain and create an array of computers to which you have just enabled remoting e.g.

$Servers = 'SERVER01','SERVER02','SERVER03','SERVER04',’SERVER05’

Using the Invoke-Command cmdlet I will demonstrate running a simple script block to collect the last boot time:

icm -ComputerName $Servers -ScriptBlock { [System.Management.ManagementDateTimeConverter]::ToDateTime((gwmi Win32_OperatingSystem).LastBootUpTime) } | ft -a pscomputername,datetime

The output of the above command should resemble Figure 3

Figure 3

Over the last year I have been using PowerShell 2.0 remoting capabilities extensively and I will follow up with a few example over the coming weeks.

For those interested I recommend the following for further reading:

Microsoft help documentation on ‘about_Remote_Troubleshooting’

http://technet.microsoft.com/en-us/library/dd347642.aspx

Windows Management Framework Core package (Windows PowerShell 2.0 and WinRM 2.0)

http://support.microsoft.com/kb/968930/en-gb

Windows Remote Management feature for Windows Server 2003 and in Windows XP

http://support.microsoft.com/kb/936059

Thanks for reading,

jfrmilner


I find myself again and again needing a constant flow of email messages to ease diagnosing message flow, so I wrote this little script to assist me. I mainly use this scripted function to test load balancing and perform latency checks but it has other benefits. One such recent example was where I needed to generating enough smtp traffic for a good sized network capture to diagnose a comms issue, this was easily achieved with the AttachmentSizeMB parameter.

I have also added comment-based help with a couple of examples, here’s the code:


Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin -ErrorAction SilentlyContinue
function Send-MailMessages {
<#

.SYNOPSIS
Generate a constant flow of messages for diagnostic purposes.

.DESCRIPTION
This script is designed to assist in generating email messages for testing external message flow to and from your messaging infrastructure.
The ability to quickly send a batch of messages with an attachment on a schedule can help track flow issues or to simply be used to confirm mail routing.

.EXAMPLE
Send-MailMessages -To Test@Test.com -From Admin@Contoso.com -MessageCount 10 -SecondsDelay 10 -AttachmentSizeMB 1

Send 10 emails to Test@Test.com every 10 seconds with a 1MB Attachment

.EXAMPLE
Send-MailMessages -MessageCount 48 -SecondsDelay 1800

Send an email every 30 minutes for 24 hours.

.LINK
https://jfrmilner.wordpress.com/2010/08/26/send-mailmessages

.NOTES
File Name: Send-MailMessages.ps1
Author: jfrmilner
Email: jfrmilner@googlemail.com
Requires: Powershell V2
Requires: Exchange Managemnent Shell (Only used to auto find the smtpServer)
Legal: This script is provided "AS IS" with no warranties or guarantees, and confers no rights. You may use, modify, reproduce, and distribute this script file in any way provided that you agree to give the original author credit.
Version: v1.0 - 2010 Aug 08 - First Version http://poshcode.org/*
Version: v1.1 - 2012 April 26 - Fixed when only a single HT Server is available. Added check for existing file. Fixed attachment parameter to use varible.

#>

param ( [Parameter(Mandatory=$false)] $To = "Test@WingtipToys.com", [Parameter(Mandatory=$false)] $From = "Postmaster@contoso.com", $AttachmentSizeMB=$null, $MessageCount=2, $SecondsDelay=10 )

$messageParameters = @{
 Body = $null | ConvertTo-Html -Body "<H2> Test Message, Please ignore </H2>" | Out-String
 From = $from
 To = $to
 SmtpServer = @(Get-TransportServer)[0].Name.ToString()
 }
if ($AttachmentSizeMB) {

if ((Test-Path $Env:TMP\$($AttachmentSizeMB)mbfile.txt) -ne $true) {
 fsutil file createnew $Env:TMP\$($AttachmentSizeMB)mbfile.txt $($AttachmentSizeMB * 1MB)
 }
$messageParameters.Add("attachment", "$Env:TMP\$($AttachmentSizeMB)mbfile.txt") }

1..$MessageCount | % { sleep -Seconds $secondsDelay ; Send-MailMessage @messageParameters -Subject ("Mailflow Test Email - " + (Get-Date).ToLongTimeString() + " Message " + $_ + " / $MessageCount") -BodyAsHtml }

}

This is an example of the what you can expect to see in the recipients mailbox:

Regards,

jfrmilner