Windows 10 has made a lot of changes from previous versions, one of which is that you can no longer view System Properties as a non-admin user. This means you can no longer view/edit your user environment variables via System Properties.

There are 3 ways around this:

  • Use another method such as set or Powershell or direct registry editing
  • Go to Control Panel->User Accounts->Change My Environment Variables
  • Run "C:\WINDOWS\System32\rundll32.exe" sysdm.cpl,EditEnvironmentVariables to envoke the Environment Variables window directly

If you’ve ever looked at configuring the Exchange Availability Service to allow cross-forest free/busy lookups you’ve probably realised that the documentation surrounding it is awful.

Get-MailboxServer | Add-ADPermission -Accessrights Extendedright -Extendedrights "ms-Exch-EPI-Token-Serialization" -User "<Remote Forest Domain>\Exchange servers"

From here, doesn’t even work for a start, because Get-MailboxServer doesn’t return the correct identity objects for Add-ADPermission. Once you’ve worked out how to get that sorted and done your

Add-AvailabilityAddressSpace -Forestname -AccessMethod PerUserFB -UseServiceAccount:$true

You’re probably thinking that you’re done, but it usually isn’t that simple. For a start if the namespaces in either forest are even a little bit complicated or you’re using a custom target address space then you’re in for some autodiscover fun – just because autodiscover works in its home forest doesn’t mean it will from your trusted partner. Before you even start, make sure your Exchange certificates are trusted by the target servers; don’t assume that just because they’re from a public CA that they will be, you never know what weird stuff has been done to the servers if you didn’t build them and they’re not under your control. Obviously if you can you should export the SCP to the partner forest with

Export-AutodiscoverConfig -TargetForestDomainController "" -TargetForestCredential (Get-Credential) -MultipleExchangeDeployments $true

But even then there are a few things that aren’t clearly documented and might trip you up; for example, you need Outlook Anywhere enabled for the Availability Service to function, which isn’t a given if you’re still running Exchange 2010 or 2007 in one or both forests. Furthermore, if one or both parties are running Exchange 2013 or 2016 and you’re not using the SCP for autodiscover then you’ll probably find the free/busy lookups fail because:

the availability service sends an Autodiscover request by using an automatically generated SMTP address for the anchor mailbox. This SMTP address that is used is [email protected]Availability_Address_Space_domain.

However, the Exchange Server 2013 Client Access server in the attendee forest cannot locate a mailbox for this email address and responds with a 404 status.

That’s right, Exchange uses an essentially random (and as far as I can tell only documented in that KB article) SMTP address for it’s autodiscover query which is rejected by the target server because, obviously, it doesn’t exist. The “fix”? Slap that SMTP address onto any old mailbox so the server returns a valid autodiscover response.

Hopefully this post will be of some help to anyone struggling to get the availability service working in their environment, I spent 2 weeks dicking about with Microsoft support trying to understand how it operates so that with any luck you won’t have to.

I’ve just completed my first ADFS 4.0 implementation and I’m quite astonished at just how many stupid, petty and easily resolved bugs it suffers from.

Case in point, if you change the service certificate it doesn’t change the https certificate bindings. This means that even though your ADFS server is using your new certificate for its communications, the web service is still using the old one. It’s not IIS any more so you have to manually recreate the bindings via netsh.

There also seems to be a bizzare issue that only affects IE whereby if your ADFS farm name DNS record is a CNAME rather than an A record, any authentication attempts will fail with a 400 BAD REQUEST error. This doesn’t affect other browsers because why would it, it’s a fucking mental thing to do.

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

<defaultProxy useDefaultCredentials="true" />

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

	Sends IM via S4B
    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)
	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
    Author: Adam Beardwood
    Date: January 13, 2016
    v1.0: Initial Release
    v1.1: Bugfix for multiple recipients


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/"

		$postparams = @{"importance"="Normal";"sessionContext"="$(([guid]::NewGuid()).guid)";"subject"="$messagesubject";"telemetryId"=$null;"to"=$recipient;"operationId"=$operationID;"_links"[email protected]{"message"[email protected]{"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
		write-verbose "Unable to send message invite"
		return $false

	write-verbose "#Check state & get Conversation ID"


		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/events?ack=$ackid" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -ContentType "application/JSON" -UseBasicParsing

		$state = (($data.content | ConvertFrom-JSON)

		if($state.gettype().name -eq "string"){
			$state = $state[-1]


		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 = ($ | ?{$_.rel -eq "conversation"}).href.split("/")[-1]

	write-verbose "#Send Messages"
		$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
		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"

		$data = Invoke-WebRequest -Uri "$rootappurl/$appid/communication/conversations/$conversationID/messaging/terminate" -Method POST -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing
		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 = "Someone forgot to set a message so you're seeing this default instead"}
if(!$messageheader){$messageheader = "This is an automated alert from "}
if(!$messagefooter){$messagefooter = "This message was sent by a bot, there's no point in replying to it."}

write-verbose "#Get Autodiscover Information"

	$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
	write-output "Unable to get autodiscover information"
	exit 1
write-verbose "#Authenticate to server"

	$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
	write-output "Unable to authenticate, verify credentials and try again"
	exit 1
write-verbose "#Get application URLs"

	$data = Invoke-WebRequest -Uri "$oauthurl" -Method GET -Headers @{"Authorization"="Bearer $authcwt"} -UseBasicParsing

	$rootappurl = ($data.content | ConvertFrom-JSON)._links.applications.href
	write-output "Unable to get Application URLs"
	exit 1

write-verbose "#Create App Instance"

	$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
	write-output "Unable to create application instance"
	exit 1

write-verbose "#Allow HTML messages to be sent"

	$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
	write-verbose "HTML Messaging Already Configured"

write-verbose "#Send messages"

$i = 0

foreach($recipient in $recipients){
	if($recipient -notmatch "^sip:\S+"){
		$recipient = "sip:$recipient"
	$msgresult = sendmessage $operationID $rootappurl $appid $authcwt $recipient $messagesubject $messagebody $messagefooter $i
		write-verbose "Message sent to $recipient"
		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;
$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”.

If you’ve configured the Server Authentication Certificate Template GPO option, which determines the certificate that the machine uses for Remote Desktop connections, and applied it to 2008 R2 or older servers then you may find that you’re getting a lot of duplicate certificates being issued. It’s a problem with an easy solution but it’s not an obvious one.

You see, if you read the documentation for the setting (something which is helpfully not included in the GPO explanation text in the GPMC) you’ll soon discover that:

You must set the certificate template’s attributes Template display name and Template name to the same value.

Due to a disparity in the way the API checks to see if a certificate already exists on the machine for this purpose if the Template Name is not the same as the Template Display name it fails to identify that it already has a matching certificate and so requests a new one.

This problem is resolved in Server 2012 R2. It’s possibly resolved in Server 2012 as well but I don’t have a box to hand I can test with.

As far as I can tell the “Do not automatically reenroll if a duplicate certificate exists in Active Directory” option has no impact on this issue.

If you’re using certificate-based authentication for your wired or wireless network and have the Lync 2013 client installed then you may find that your users start getting prompted to select a certificate when connecting to the network for the first time. This is because the Lync client issues users certificates with blank Subject fields and Windows can’t work out which certificate to use.

There’s a hotfix available from Microsoft here: but unfortunately it’s not available via WSUS so you’ll have to push it to your clients “manually”. Personally I would have thought that certificate-based wireless authentication in environments running Lync were relatively common, enough to justify a proper patch, but apparently not.

Update: Fixed it for a while, then it broke again. Come to the conclusion that the Windows 7 Task Scheduler is just irreparably broken as I’ve had this happen time and time again. Some 3rd party application is interacting with it in a way that reliably breaks it and I’ve never been able to narrow it down.

Yes, this one again.

When you open up the Task Scheduler, you get a message that says:
“The selected task “{0}” no longer exists. To see the current tasks, click Refresh.”

And you can’t view half your tasks any more, though if you run a SCHTASKS /Query /FO LIST you’ll see that they’re all still there and seem to be working.

I’ve toyed around with a lot of different solutions for this, but I’ve finally found one that fixed it for me:

Go to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Tracing\SCM\Regular and set or add REG_DWORD TracingDisabled to 0 and then reboot.

My only concern is that this may be generating a trace log somewhere, but I’ve been unable to find anything that suggests this is the case so far.