Category: Scripts

Second-hop PowerShell remoting with Skype for Business/Lync cmdlets

I have faced a huge issue trying to run mutliple commands via remote PowerShell on different Front End servers, but apparently if the cmdlet needs to access resources from the Back End database – the command fails with this:

Active Directory error "-2147016672" occurred while searching for domain controllers

which is a very frustrating error.

For example running Get-CsCertificate will work, as the information is saved locally on the Front End, however Test-CsDatabase -LocalService will fail with this error, because executing this from your machine (in my case in a different domain) first jumps on the Front End, then requires a token from the Front End’s AD to send to the 3rd server for authentication – the SQL Back End where the connection breaks.

I also experimented with Get-CsConnections.ps1 script, and got the same problem.

So I came up with a bit ugly, but working solution for this. It is simple – Scheduled tasks.

$trigger = New-ScheduledTaskTrigger -AtStartup
$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument 'Import-Module SkypeForBusiness; Test-CsDatabase -LocalService | convertto-csv -notypeinformation | out-file C:\temp\db.txt' 
$principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\NETWORK SERVICE" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet
$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings -Principal $principal
Register-ScheduledTask Test-CsDatabaseOnce -InputObject $task
Start-ScheduledTask -TaskName Test-CsDatabaseOnce
do {
    $state = (Get-ScheduledTask -TaskName Test-CsDatabaseOnce).State
    Start-Sleep -Milliseconds 200
} while ($state -eq "Running")
Get-Content C:\temp\db.txt | select -skip 1
Unregister-ScheduledTask -TaskName Test-CsDatabaseOnce -Confirm:$false

Here I create a simple task with the Network Service account and the name Test-CsDatabaseOnce, then run it, wait for the task to complete, which exports my results in csv in C:\temp\db.txt, and finally I get my results and work with them further.

Did the same thing with Get-CsConnections.ps1, however before that I had to deploy the script locally on each Front End with the simple code below:

$local_file = "C:\temp\Get-CsConnections.ps1"
$content = [system.io.file]::ReadAllBytes($local_file)
$remote_file = "C:\temp\Get-CsConnections.ps1"
Invoke-Command -Session $session -ScriptBlock { Param($path) [system.IO.file]::WriteAllBytes($path,$using:content) } -ArgumentList $remote_file

PowerShell get variable names from PSCustomObject

If you for example import a .csv file and loop trough it with with PowerShell, each entry is from the type PSCustomObject.

Recently I wanted to see an easy way to get the column names from the .csv file as well as the values, so I found out this very easy solution:

$csv | ForEach-Object {
 $row = $_;
 #give an array of columns for this item
 $columns = ($row.PSObject.Properties.Name);
 #gives an array of the values for this item
 $data = $row.psobject.Properties.Value;
}

I also wanted to do a comma separated list of the values, which I did with the following snippet:

 $commas_columns = $columns -join ",";
 $commas_data = "'{0}'" -f ($data -join "','");

Office 365 License Management for Skype for Business Online

So I had a customer recently that wanted to update licenses on multiple users but only for Skype for Business Online. As I was researching I found out that in the web panel there is no option to that in bulk. Also there is no easy option to do in the PowerShell too. So I had to come out with a simple and fast solution so that I could enable all users only for Skype for Business Online at once. Another special requirement was to ONLY enable the Skype for Business service and ONLY for users having the E3 license assigned.

As you might know, Office 365 License has multiple services in it, so you can have an E3 user with Skype enabled and another one with Skype disabled. Gets trickier because there is not option to just ‘enable’ or ‘disable’ a service for a user (as you have the option in the admin panel clicking with your mouse user after user…). The only provided way with PowerShell is to create license options, per license, specifying which services should be disabled, by default all remaining services are deemed as enabled.

First you need to get the account sku with the following command:

Get-MsolAccountSku

You will receive a table with fist collumn AccountSkuId, you need the text before the colon, so for example mytennant365:SOME_SERVICE.

I will explain how the script works first, then you can review it yourself below.

  1. I have a .csv file called files.csv with only 1 column, with the name WindowsEmailAddress, containing the SMTP addresses of the users that need to be updated
  2. Loop trough all users and and get the licenses assigned (you can have multiple licenses assigned to a user!)
  3. Loop trough all licenses and only filter the E3 one (ENTERPRISEPACK) and get all services with their status (being disabled, enabled, etc.)
  4. Create an empty array of disabled services (options)
  5. Loop trough all services and copy only the disabled plans to the new empty array with disabled options, but SKIP the Skype for Business Online one (MCOSTANDARD).
  6. Create a service license option with the disabled services (skipping the Skype for Business Online one)
  7. Assign the newly created license options to the user
$users = Import-Csv file.csv
$users | ForEach-Object {
	$upn = $_.WindowsEmailAddress;
	$l = (Get-MsolUser -UserPrincipalName $upn).Licenses;
	$l | ForEach-Object {
		if ($_.AccountSkuId -eq "mytennant365:ENTERPRISEPACK"){
			$p = $_.ServiceStatus;
			$DisabledOptions = @()
			$p | ForEach-Object {
				if ($_.ProvisioningStatus -eq "Disabled" -and $_.ServicePlan.ServiceName -ne "MCOSTANDARD"){
					$DisabledOptions += $_.ServicePlan.ServiceName
				}
			}
			$x = New-MsolLicenseOptions -AccountSkuId "mytennant365:ENTERPRISEPACK" -DisabledPlans $DisabledOptions
			Set-MsolUserLicense -UserPrincipalName $upn -LicenseOptions $x;
		}
	}
}

The WinRM client cannot process the request

When trying to login to Office 365 with New-PSSession you might reiceve the following error:

New-PSSession : [admin1e.online.lync.com] Connecting to remote server admin1e.online.lync.com failed with the following error message : The WinRM client cannot process the request. Basic authentication is currently disabled in the client configuration. Change the
client configuration and try the request again.

So I created a small cmd snippet that resolves this (you need local administrator rights to do it):

reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client /t REG_DWORD /v AllowBasic /d 1 /f
reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\WinRM\Service /t REG_DWORD /v AllowBasic /d 1 /f
winrm set winrm/config/service/auth @{Basic="true"}

Export Contacts from Exchange 2013 (with Impersonate rights)

So this is how I managed to export contacts SMTP addresses from a exchange 2013 mailbox using an admin user with impersonate rights:

#admin should have impersonate rights
$admin = "admin@domain.com"
$user = "user@domain.com"
#First Find Microsoft.Exchange.WebServices.dll location
$dllpath = "D:\Exchange Server\Bin\Microsoft.Exchange.WebServices.dll"
Import-Module $dllpath
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
#admin account credentials
$psCred = Get-Credential
$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(), $psCred.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
$service.AutodiscoverUrl($admin ,{$true})
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$user);
$contactsFolder = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$user)
$contacts = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$contactsFolder)
$view = new-object Microsoft.Exchange.WebServices.Data.ItemView($contacts.TotalCount, 0)
$results = $contacts.FindItems($view)
$response = $service.LoadPropertiesForItems($results, [Microsoft.Exchange.WebServices.Data.PropertySet]::FirstClassProperties)
foreach ($item in $results){
echo $item.Item.EmailAddresses[[Microsoft.Exchange.WebServices.Data.EmailAddressKey]::EmailAddress1].Address
}

A simple agent to convert log.nsf file to plain text file

You should also have a view, called LTT with no categorisation and showing all log entries you need exported.​
Sub Initialize
	Dim session As New NotesSession
	Dim db As NotesDatabase
	Dim view As NotesView
	Dim doc As NotesDocument
	Dim fileNum As Integer
	Set db = session.Currentdatabase
	Set view = db.Getview("LTT")
	Set doc = view.GetFirstDocument
	fileNum% = FreeFile()
	Open "C:\log.txt" For Append As fileNum% 	
	While Not ( doc Is Nothing )
		ForAll v In doc.EventList
			Print #fileNum% , v
		End ForAll
		Set doc = view.GetNextDocument(doc)
	Wend
	Close fileNum%​
End Sub​
Data is exported to C:\log.txt

A regex to match Domino FQDN

​A simple regex that can match a Domino FQDN:

([ A-Za-z0-9._%+-]+)(\/\b[A-Za-z0-9._%+-]+)+

First matching group​ ([ A-Za-z0-9._%+-]+) captures names with only first, first + last or first + middle + last names.

The second matching group (\/\b[A-Za-z0-9._%+-]+)+ captures a space-free “/Org” pattern multiple times.

Non-persistent keyboard layouts

I found out that my keyboard layouts were not staying in VDI after I log off. So I had to automate this and not lose my time with adding my layouts every day.

So you simply create a .cmd file with the following content:

control intl.cpl,, /f:”DRIVE:\FULL\PATH\TO\FILE.XML”​

Then you create a FILE.XML in that location, containing the folowing XML:

<gs:GlobalizationServices xmlns:gs="urn:longhornGlobalizationUnattend">
    <gs:UserList>
        <gs:User UserID="Current" CopySettingsToDefaultUserAcct="true" CopySettingsToSystemAcct="true"/></gs:UserList>
    <gs:InputPreferences>
        <gs:InputLanguageID Action="add" ID="0402:00040402"/>
    ​</gs:InputPreferences>
</gs:GlobalizationServices>

​The 00040402​ is the hex code identifier of the keyboard layout you need (in this case 0x00040402 – Bulgarian (phonetic traditional).

This article has all the keyboard hexadecimal identifiers that one might need:

https://technet.microsoft.com/en-us/library/hh825684.aspx​

Then you just open the windows scheduler and add this .cmd to execute on Log On and on Work station Unlock and add to Window Startup folder and there you go!