You know how annoying it is when you return some information in Powershell that includes a list of items and the console helpfully truncates it with a …

 

 

 

Whereas what you really want is for it to just show the whole thing like:

 

 

 

Well you can. The truncation is controlled by $FormatEnumerationLimit and if you set it to -1 it won’t truncate output at all. The default for a standard Powershell instance is 4, the Exchange Management Shell ups this to 16 and other console files may make their own modifications.

Simple.



We all know how annoying it is working somewhere with a proxy server that requires authentication, especially as Microsoft increasingly don’t support the scenario with many of their Azure-related tools. However, it is quite possible to use authenticated proxies with .NET applications including Powershell.

For the former, edit the application .config file and add

<system.net>
<defaultProxy useDefaultCredentials="true" />
</system.net>

And for Powershell, add the following to your scripts or $profile

$proxyString = "http://proxy:8080"
$proxyUri = new-object System.Uri($proxyString)
 
[System.Net.WebRequest]::DefaultWebProxy = new-object System.Net.WebProxy ($proxyUri, $true)
[System.Net.WebRequest]::DefaultWebProxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials


This script uses UCWA to send IMs via Skype For Business. Unlike the commonly documented methods using Microsoft.Lync.Model from the Lync SDK which require the Lync/S4B client to be installed, running and logged in to work, this will run from any machine on your internal network (and in theory could be used externally with some tweaking too). It still needs some cleaning up because none of the code examples I could find were for Powershell so there was a bit of trial and error and I’m sure things can be done more efficiently, but at a basic level it does what it’s supposed to.

The help should cover most of the arguments, it’s all fairly self-explanatory. You’ll probably want to update the following line in the script with your own Application name and GUID:

$postparams = @{UserAgent="My UCWA Application";EndpointId="75d0449f-aa09-4f5d-add5-eeefc518c48a";Culture="en-US"} | ConvertTo-JSON

You can use ([guid]::NewGuid()).guid to generate a new GUID for your application.

You can also edit the default $messageheader, $messagefooter, $messagebody & $messagesubject values. The Subject and Header must be plaintext, the Body and Footer support HTML.

#Requires -version 4
 
<#
.SYNOPSIS
	Sends IM via S4B
.DESCRIPTION
    Sends IM via S4B using UCWA
.PARAMETER username
	Username of account used to send messages in UPN format
.PARAMETER password
	Password of account as plaintext (exclusive of pwd)
.PARAMETER pwd
	Password of account as securestring (exclusive of password)
.PARAMETER recipients
	List of recipient SIP addresses, comma separated
.PARAMETER messagesubject
	Message subject (Plain text only)
.PARAMETER messagebody
	Message body (HTML allowed)
.PARAMETER messageheader
	Message header (Plain text only), sent as part of the invitation
.PARAMETER messagefooter
	Message footer (HTML allowed), sent after the message body
.EXAMPLE
    C:\>Send-S4BMessage.ps1
.NOTES
    Author: Adam Beardwood
    Date: January 13, 2016
    v1.0: Initial Release
    v1.1: Bugfix for multiple recipients
#>
 
[CmdletBinding(DefaultParameterSetName="secure")]
param(
	[Parameter(Mandatory=$true)][string]$username,
	[Parameter(Mandatory=$true,ParameterSetName='secure')][System.Security.SecureString]$pwd,
	[Parameter(Mandatory=$true,ParameterSetName='plain')][string]$password,
	[Parameter(Mandatory=$true)][array]$recipients,
	[Parameter(Mandatory=$false)][string]$messagesubject,
	[Parameter(Mandatory=$false)][string]$messagebody,
	[Parameter(Mandatory=$false)][string]$messageheader,
	[Parameter(Mandatory=$false)][string]$messagefooter
)
 
function sendmessage ($operationID, $rootappurl, $appid, $authcwt, $recipient, $messagesubject, $messagebody, $messagefooter, $ackid) {
 
	$statecount = 0
	$ackid = $ackid.tostring()
 
	write-verbose "#Send Message Invite"
 
	write-verbose "$rootappurl/$appid/communication/"
 
	try{
		$postparams = @{"importance"="Normal";"sessionContext"="$(([guid]::NewGuid()).guid)";"subject"="$messagesubject";"telemetryId"=$null;"to"=$recipient;"operationId"=$operationID;"_links"=@{"message"=@{"href"="data:text/plain,$messageheader"}}} | convertto-json
 
		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/messagingInvitations" -Method POST -Body "$postparams" -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/json" -UseBasicParsing
	}catch{
		write-verbose "Unable to send message invite"
		return $false
	}
 
	write-verbose "#Check state & get Conversation ID"
 
	do{
 
		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/events?ack=$ackid" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/JSON" -UseBasicParsing
 
		$state = (($data.content | ConvertFrom-JSON).sender.events._embedded.messaging.state)
 
		if($state.gettype().name -eq "string"){
 
		}else{
			$state = $state[-1]
		}
 
		$statecount++
 
		if(($statecount -ge 25) -or ($state -eq "Disconnected")){
			write-verbose "No response from endpoint or conversation declined"
			return $false
		}
 
		start-sleep 1
 
	}while($state -notcontains "Connected")
 
	$JSONdata = $data.content | ConvertFrom-JSON 
 
	$conversationID = ($JSONdata.sender.events.link | ?{$_.rel -eq "conversation"}).href.split("/")[-1]
 
	$conversationID
 
	write-verbose "#Send Messages"
 
	try{
		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/messages" -Method POST -Body $messagebody -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "text/HTML" -UseBasicParsing
		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/messages" -Method POST -Body $messagefooter -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "text/HTML" -UseBasicParsing
	}catch{
		write-verbose "Unable to send message"
		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/terminate" -Method POST -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
		return $false
	}
	write-verbose "#Terminate Conversation"
 
	try{
		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/terminate" -Method POST -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
	}catch{
		write-verbose "Failed to terminate conversation"
		return $false
	}
 
	rv conversationID, recipient, ackid, state
 
	return $true
 
}
 
if($pwd){[string]$password = (New-Object System.Management.Automation.PSCredential('dummy',$pwd)).getnetworkcredential().password}
if(!$messagesubject){$messagesubject = "S4B Automated Message"}
if(!$messagebody){$messagebody = "<font face='calibri'>Someone forgot to set a message so you're seeing this default instead</font>"}
if(!$messageheader){$messageheader = "This is an automated alert from <system>"}
if(!$messagefooter){$messagefooter = "<font face='calibri'><i>This message was sent by a bot, there's no point in replying to it.</i></font>"}
 
write-verbose "#Get Autodiscover Information"
 
try{
	$data = Invoke-WebRequest -Uri "https://lyncdiscoverinternal.$($env:userdnsdomain)" -Method GET -ContentType "application/json" -UseBasicParsing
 
	$baseurl = (($data.content | ConvertFrom-JSON)._links.user.href).split("/")[0..2] -join "/"
 
	$oauthurl = ($data.content | convertfrom-json)._links.user.href
}catch{
	write-output "Unable to get autodiscover information"
	exit 1
}
write-verbose "#Authenticate to server"
 
try{
	$postParams = @{grant_type="password";username=$username;password=$password}
 
	$data = Invoke-WebRequest -Uri "$baseurl/WebTicket/oauthtoken" -Method POST -Body $postParams -UseBasicParsing
 
	$authcwt = ($data.content | ConvertFrom-JSON).access_token
}catch{
	write-output "Unable to authenticate, verify credentials and try again"
	exit 1
}
write-verbose "#Get application URLs"
 
try{
	$data = Invoke-WebRequest -Uri "$oauthurl" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
 
	$rootappurl = ($data.content | ConvertFrom-JSON)._links.applications.href
}catch{
	write-output "Unable to get Application URLs"
	exit 1
}
 
write-verbose "#Create App Instance"
 
try{
	$postparams = @{UserAgent="My UCWA Application";EndpointId="75d0449f-aa09-4f5d-add5-eeefc518c48a";Culture="en-US"} | ConvertTo-JSON
 
	$data = Invoke-WebRequest -Uri "$rootappurl" -Method POST -Body "$postparams" -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/json" -UseBasicParsing
 
	$appurl = $(($data.content | ConvertFrom-JSON)._links.self.href)
 
	$appurl = "$($rootappurl.split("/")[0..2] -join "/")$(($data.content | ConvertFrom-JSON)._links.self.href)"
 
	$appid = $appurl.split("/")[-1]
 
	$operationID = (($data.content | ConvertFrom-JSON)._embedded.communication | GM -Type Noteproperty)[0].name
}catch{
	write-output "Unable to create application instance"
	exit 1
}
 
write-verbose "#Allow HTML messages to be sent"
 
try{
	$postparams = @{"supportedMessageFormats"="Plain","Html"} | ConvertTo-JSON
 
	$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/makeMeAvailable" -Method POST -Body $postparams -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/json" -UseBasicParsing
}catch{
	write-verbose "HTML Messaging Already Configured"
}
 
write-verbose "#Send messages"
 
$i = 0
 
foreach($recipient in $recipients){
 
	if($recipient -notmatch "^sip:\S+"){
		$recipient = "sip:$recipient"
	}
	$i++
	$msgresult = sendmessage $operationID $rootappurl $appid $authcwt $recipient $messagesubject $messagebody $messagefooter $i
	if($msgresult){
		write-verbose "Message sent to $recipient"
	}else{
		write-verbose "Message not sent to $recipient"
	}
	start-sleep 1
}
 
write-verbose "#Delete App Instance"
$deleteapp = Invoke-WebRequest -Uri "$rootappurl/$appid" -Method DELETE -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing


Let’s say you make a change to your locale settings by directly editing the registry, modifying the HKCU\Control Panel\International\sTimeFormat key. Problem is that Windows doesn’t pick up these changes until you log off and back on again or restart explorer.exe. Now if you make the changes via Control Panel you don’t have to do this, so why do you if you modify the registry?

Well, when you use the UI to make changes to the locale or any other policy or environment settings, Windows sends a WM_SETTINGCHANGE broadcast to all Windows notifying them of the change to settings so they can refresh their config and you can do it too!

if (-not ("win32.nativemethods" -as [type])) {
    add-type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(
    IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,
    uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
"@
}
 
$HWND_BROADCAST = [intptr]0xffff;
$WM_SETTINGCHANGE = 0x1a;
$result = [uintptr]::zero
 
[win32.nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE,[uintptr]::Zero, "Environment", 2, 5000, [ref]$result);

From the docs in reference to the lParam parameter of SendMessageTimeout, set to “Environment” in the above example:

This string can be the name of a registry key or the name of a section in the Win.ini file. When the string is a registry name, it typically indicates only the leaf node in the registry, not the full path.

When the system sends this message as a result of a change in policy settings, this parameter points to the string “Policy”.

When the system sends this message as a result of a change in locale settings, this parameter points to the string “intl”.

To effect a change in the environment variables for the system or the user, broadcast this message with lParam set to the string “Environment”.



Scenario: During an AD migration I needed to remove all of the certificates from a migrated user’s local store which had been issued by the old domain’s CA. Not simply for housekeeping reasons but because the new domain makes use of credential roaming and we didn’t want a load of old certificates taking up space in AD for no reason.

The following code will remove all certificates issued by from the Personal (My) store of the currently logged in user. If you wanted to narrow the criteria you can also filter on any of: Subject, Issuer, Thumbprint, FriendlyName, NotBefore, NotAfter or Extensions. You can also target different containers and switch between User (CurrentUser) and Machine (LocalMachine) certificate stores. As far as I’m aware there’s no way to do this for a user that isn’t currently logged in.

$Store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","Currentuser")
$store.Open("MaxAllowed")
$certs = $store.certificates | ?{$_.Issuer -eq "CN=My Issuing CA 1, DC=my, DC=domain"}
$certs | %{$store.remove($_)}
$Store.close()

See also https://www.angryadmin.co.uk/?p=600



As per http://support.microsoft.com/kb/2921141, you cannot install the Exchange 2013 management tools onto a machine running Terminal Services. It’s unclear why.

Sure, it’s not as important as it used to be, what with the EAC now being a web interface, but it does mean you can’t easily install the Powershell modules and have to rely on Powershell Remoting which works fine but is much more of a pain in the arse to set up.



I’m amazed that I haven’t previously had a need for something like this, but I was looking for some way to visualise AD group memberships, specifically to take into account fairly deeply nested groups. After a fair bit of searching, a lot of dead-ends and some products that seriously over-sold themselves, I came across this little beauty:

https://gallery.technet.microsoft.com/scriptcenter/Graph-Nested-AD-Security-eaa01644

It’s a Powershell module which extracts group memberships for a User, Group or OU (well, everything in that OU anyway) and creates a Graphviz file that gives a functional, if not very pretty, visualisation of the group membership hierarchy. The output looks something like this:

Draw-ADSecurityGroupNesting
Sample output

Extremely handy if you’re trying to get a better idea of how your group nesting shakes out or where you may have circular memberships or redundant groups.



Quick and easy; Exchange creates an environment variable called “ExchangeInstallPath” which holds the install path for Exchange on a given server, this can be accessed via Powershell using $env:ExchangeInstallPath.

This can be useful if you need to call elements such as RemoteExchange.ps1 but aren’t sure if Exchange has been installed to the default location.



Quick and easy one-liner – if you need to know at a glance which DCs in a domain are GCs and which aren’t:

[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().DomainControllers | %{"$($_.Name) : $($_.isglobalcatalog())"}


I saw this in someone’s signature on one of the Technet forums and though it was a really clever idea; nobody wants to put their email address in plaintext on the internet because it’ll just get hoovered up by spammers and most common obfuscation techniques are easily worked around by spambots, so how about…

[string](0..21|%{[char][int](23+("74778682874174878091987477868287237688239484").substring(($_*2),2))})-replace " "

Clever, isn’t it.

You can generate your own with the following code:

$out = $null
$email = "user@example.com"
foreach($char in $($email.tochararray())){$out += @([System.Convert]::ToUInt32($char)-23)}
$string = [string]$out -replace " "
"Your code is: [string](0..$($($out.length)-1)|%{[char][int](23+(`"$string`").substring((`$_*2),2))})-replace `" `""

Which should give an output that looks something like:

[string](0..15|%{[char][int](23+("94927891417897748689857823768886").substring(($_*2),2))})-replace " "

Email addresses with certain symbol characters may not encode properly.