EnableSessionTicket from Powershell

To get support for Lync and Skype for Business on Windows Server 2012 R2, you need to add a registry key that changes TLS session caching on 2012 R2 Server. This is described here https://support.microsoft.com/en-us/kb/2901554

To create this key, use the following powershell oneliner:

PS Oneliners: Set Skype4B services to manual or automatic startup

If you need to reboot a Skype for Business server, you might not always want the services to start automatically afterwords for various reasons. For instance if you are doing a shutdown of an entire pool, you’d want to run

Start-CsPool -PoolFqdn skypepool.contoso.com

to do a cold start of the pool, instead of the services starting automatically.

Use these oneliners to set the services to manual startup and back again to automatic afterwords.

Managing Acano Server in Powershell

v0.2 has been released! Post here

I love it when stuff is possible to manage through PowerShell.

I’ve been working quite a bit with Acano lately and because of that I have started looking in to the management API that they provide on their Server. This API is exposed as XML through HTTPS, so I thought that it should be quite possible to write some PowerShell functions that accessed parts of the API. These have evolved in to what I now release as version 0.1 of the PsAcano PowerShell implementation of the Acano API.

Currently only the GET commands are implemented, so it is only possible to view information at the moment – not edit or create anything. The functionality provided by the POST, PUT and DELETE commands will  be implemented in the coming days and weeks.

Last weekend Knowledge Factory had our kick off in beautiful Vaxholm outside of Stockholm. There we were treated to an extremely inspiring session by Simon Wåhlin (http://blog.simonw.se/) about PowerShell and GIT. A big thank you to Simon for finally kicking me into doing source control on my scripts 😀

I’ve set up an account on Github, and from now on my scripts will be available there, and this of course also applies to PsAcano.

If you don’t want to visit the repository page on github, you can download the module here. Installation instructions can be found in the Readme.md file. Feedback is welcome as issues on github or comments on this blogpost.

Script: Find-CsLineUri

Often I need to find a certain LineUri in Lync. LineUris are most of the time configured on users, so a simple

Get-CsUser -filter {LineUri -like "tel:<e164number>*"}

will give you the result you are looking for. But a user is not the only object in Lync that can have a LineUri, and if you in addition don’t know what object has the LineUri you might need to search through all the objects. This script does just that. Sort of an opposite to Ståle Hansens List-UnusedNumbers.ps1.

The script will also list all objects that have that LineUri, so it is useful in the situations where you have managed to get the same LineUri on two objects and are getting “SIP/2.0 485 Ambiguous” errors on calls. Normally, if you try to give an object a LineUri that is already configured it will throw a powershell error, but there are situations where it doesn’t pick up on it, especially when using the ;ext=1234 addition to the LineUri.

Lync reports a SIP 485 Ambiguous and a Diagnostic ID 4199 "Multiple users associated with the target phone number"
Lync reports a SIP 485 Ambiguous and a Diagnostic ID 4199 “Multiple users associated with the target phone number”

Download Find-CSLineUri.zip or copy the sourcecode:

####################################################################################################
# Find-CsLineUri.ps1
#
# Lists all objects with a given LineUri
#
#
# Passing parameters:
# .\Find-CsLineUri.ps1 +4712345678
#
# Written by Tom-Inge Larsen (http://www.codesalot.com)
#
####################################################################################################
param($getlineuri)

Function ListContents
{
    Param($Heading,$list)

    Write-Host $Heading
    Write-Host "--------------------------------------------------"
    Write-Host

    foreach ($object in $list) {
       $object
    }
}

clear-host

write-debug $getlineuri

$getlineuri = "tel:" + $getlineuri + "*"

Write-debug $getlineuri

$csusers=Get-CsUser -Filter {LineURI -like $getlineuri} | Select-Object SipAddress,LineURI | out-string -stream
$csuserspl=Get-CsUser -Filter {PrivateLine -like $getlineuri} | Select-Object SipAddress,PrivateLine | out-string -stream
$csanalogs=Get-CsAnalogDevice -Filter {LineURI -like $getlineuri} | Select-Object SipAddress,LineURI | out-string -stream
$cscaps=Get-CsCommonAreaPhone -Filter {LineURI -like $getlineuri} | Select-Object SipAddress,LineURI | out-string -stream
$csums=Get-CsExUmContact -Filter {LineURI -like $getlineuri} | Select-Object SipAddress,LineURI | out-string -stream
$csdialins=Get-CsDialInConferencingAccessNumber -Filter {LineURI -like $getlineuri} | Select-Object PrimaryUri,LineURI | out-string -stream
$cstrusteds=Get-CsTrustedApplicationEndpoint -Filter {LineURI -like $getlineuri} | Select-Object SipAddress,LineURI | out-string -stream
$csrgses=Get-CsRgsWorkflow | Where-Object {$_.LineUri -like $getlineuri} | Select-Object PrimaryUri,LineURI | out-string -stream

if ($csusers -ne $null){ListContents -Heading "User" -list $csusers}
if ($csuserspl -ne $null){ListContents -Heading "Private Line" -list $csuserspl}
if ($csanalogs -ne $null){ListContents -Heading "Analog Device" -list $csanalogs}
if ($cscaps -ne $null){ListContents -Heading "Common Area Phone" -list $cscaps}
if ($csums -ne $null){ListContents -Heading "Exchange UM Contact" -list $csums}
if ($csdialins -ne $null){ListContents -Heading "Dial-in Conference Number" -list $csdialins}
if ($cstrusteds -ne $null){ListContents -Heading "Trusted Application Endpoint" -list $cstrusteds}
if ($csrgses -ne $null){ListContents -Heading "Response Group Workflow" -list $csrgses}

Making Office Web Apps Server private

After installing the WAC server following this or another guide, we also normally publish the WAC server through the same reverse proxy method as we do the other Lync web services – using the DNS name that we configured when we created the farm.

If you publish the WAC server this way without doing anything else it will work the same way for users on the internet as it does for the internal users and we might be content with that. But should we?

My colleague Marjus made this post a little while back on how to limit the access to the WAC services only to servers from your domain. If you don’t do this you’re basically publishing the WAC server as a public service that anyone can add to topology builder and use as the WAC server in their own Lync environment.

So, if you want to keep your WAC server for yourself, remember to add all domains where you host servers that should be able to use the WAC server to the allow list by using the New-OfficeWebAppsHost Cmdlet. The wildcard * is assumed on all domains in the allow list, so subdomains are supported automatically. You only need to add the server domain(s), not necessarily the same as the SIP domain(s).

New-OfficeWebAppsHost -Domain "contoso.com"

Script: Create live tiles that changes power scheme

After i got my Surface Pro, I’ve more often than before found myself needing to change between power schemes. On my laptop, I’ll usually set it to “Max performance” and just leave it there, but on the Surface it’s necessary to conserve power a bit more.

I’ve thought about making a live tile to do this, so I wrote a PowerShell script that will create one live tile for each configured powerscheme on the machine and pins it to the start screen. The code is based on a codesample for creating shutdown tiles, and this ScriptingGuy post. The script needs to be run as Administrator. Enjoy!

Download Create-PowerSchemeTiles.zip or copy the sourcecode:


#requires -Version 3.0

#####################################################################################
# Create-PowerSchemeTiles.ps1
#
# Creates live tiles for all configured power schemes on the machine and pins them to
# the start screen.
#
#
# Usage:
# .Create-PowerSchemeTiles.ps1
#
# Written by Tom-Inge Larsen (<a href="http://www.codesalot.com">http://www.codesalot.com</a>)
#
#####################################################################################

Function CreatePowerSchemeTile
{
    Param
    (
        [parameter(Mandatory=$true)][String[]]$SchemeGUID,
        [parameter(Mandatory=$true)][String[]]$SchemeName
    )
Write-Verbose "Creating Windows shutdown tile to Start menu."

#create a new shortcut
$ShortcutPath = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\" + $SchemeName + ".lnk"
$Shortcut = $WshShell.CreateShortcut($ShortcutPath)
$Shortcut.TargetPath = "$env:SystemRoot\System32\powercfg.exe"
$arguments = "-s " + $SchemeGUID
$Shortcut.Arguments = $arguments
$Shortcut.Save()

#change the default icon of shortcut
$Lnk = $Desktop.ParseName($ShortcutPath)
$LnkPath = $Lnk.GetLink
$LnkPath.SetIconLocation("$env:SystemRoot\System32\ddores.dll",20)
$LnkPath.Save()

#pin application to windows Start menu
$Verbs = $Lnk.Verbs()
Foreach($Verb in $Verbs) {
    If($Verb.Name.Replace("&","") -match "Pin to Start") {
        $Verb.DoIt()
    }
}

If(Test-Path -Path $ShortcutPath) {
    Write-Host "Create" $SchemeName "tile successfully." -ForegroundColor Green
    } Else {
    Write-Host "Failed to create" $SchemeName "tile." -ForegroundColor Red
   }
}

$Shell = New-Object -ComObject Shell.Application
$Desktop = $Shell.NameSpace(0X0)
$WshShell = New-Object -comObject WScript.Shell
$plans = Get-WmiObject -Class win32_powerplan -Namespace root\cimv2\power
$regex = [regex]"{(.*?)}$"
foreach ($plan in $plans) {
    $planGuid = $regex.Match($plan.instanceID.Tostring()).groups[1].value
    $planName = $plan.ElementName.Tostring()
    Write-Debug $planGuid
    Write-Debug $planName
    CreatePowerSchemeTile -SchemeGUID $planGuid -SchemeName $planName
}

Adding external contacts to the #Lync addressbook

There are some scenarios where it would be nice to be able to add external contacts to the Lync addresse so that they are searchable for everyone in the organization. Or at least external in the sence that the contact has a sip domain that isn’t supported in the Lync topology.

One such scenario could be an integration with an internal Cisco Telepresence solution.

The way to solve this is to add a contact object to AD that has the msRTCSIP-PrimaryUserAddress attribute populated, and the contact should be added to the address book on the next synchronization pass. I’ve made a script to create this kind of object:

Note that displayName is not required, but if you don’t add it the contact will only display the sipadress in the Lync client. I guess it’s also possible to append other AD attributes to the user such as telephoneNumber.

I got a question about telephoneNumber as well, so I’ve added it as an optional parameter in the script.
Download latest version here – Contains both versions and an example .csv

Or copy the sourcecode:


#####################################################################################
 # New-SipContact.ps1
 #
 # Creates a contact object in AD that will be included in Lync/OCS address books.
 #
 #
 # Passing parameters:
 # .New-SipContact.ps1 -cn "John Spencer" -OUpath "OU=SIPContacts,DC=contoso,DC=com" -sipaddress "john.spencer@litwareinc.com" -displayname "John Displayname Spencer"
 #
 # Written by Tom-Inge Larsen (http://www.codesalot.com)
 #
 #####################################################################################
 param($cn,$OUpath,$sipAddress,$displayName="",$telephoneNumber="")

$fullpath= "LDAP://" + $OUpath
 $SIPContactOU = [ADSI]$fullpath

$SIPContact = $SIPContactOU.create("contact", "cn=" + $cn)
 $SIPContact.Put("Description","SIP Contact Object")
 $SIPContact.Put("msRTCSIP-PrimaryUserAddress", "sip:" + $sipAddress)
 if ($displayName -ne "") {
 $SIPContact.Put("displayName", $displayName)
 }
 $SIPContact.Put("msRTCSIP-PrimaryUserAddress", "sip:" + $sipAddress)
 if ($telephoneNumber -ne "") {
 $SIPContact.Put("telephoneNumber", $telephoneNumber)
 }
 $SIPContact.setInfo()
 

I’ve also created one to bulk create contacts from a .csv file


#####################################################################################
 # New-SipContactBulk.ps1
 #
 # Creates a contact object in AD that will be included in Lync/OCS address books.
 #
 #
 # Passing parameters:
 # .New-SipContactBulk.ps1 -OUpath "OU=SIPContacts,DC=contoso,DC=com" -csv "c:newcontacts.csv"
 #
 #
 # Written by Tom-Inge Larsen (http://www.codesalot.com)
 #
 #####################################################################################
 param($OUpath,$csv)

$fullpath= "LDAP://" + $OUpath
 $SIPContactOU = [ADSI]$fullpath

$contacts = Import-Csv -path $csv -header "cn","displayName","sipaddress"

foreach ($contact in $contacts) {

$SIPContact = $SIPContactOU.create("contact", "cn=" + $contact.cn)
 $SIPContact.Put("Description","SIP Contact Object")
 $SIPContact.Put("msRTCSIP-PrimaryUserAddress", "sip:" + $contact.sipaddress)
 if ($contact.displayname -ne "") {
 $SIPContact.Put("displayName", $contact.displayName)
 }
 $SIPContact.setInfo()
 }

The example csv file contains this:

John Spencer,,john.spencer@litwareinc.com
Spencer John,Spencer Displayname John,spencer.john@litwareinc.com