Powershell

Connecting to Skype for Business Online via PowerShell in a Hybrid Environment

How to connect to Skype for Business Online via Powershell

To connect to Skype for Business Online:

  • Download and install the module from Microsoft

  • Run the following commands in PowerShell:

    Import-Module SkypeOnlineConnector

    $sfboSession = New-CsOnlineSession -UserName “[email protected]” -OverrideAdminDomain “yourtennant.onmicrosoft.com”

    Import-PSSession $sfboSession

    Applies To : Windows 10, Windows Server


    I’ve been caught out by this twice and it’s taken me a while to find the rather simple answer.

    Most instructions give you a pretty simple way to connect to Skype for Business Online (or they’ll just call it Skype for Business). You install the module via executable, downloaded from Microsoft, and then try to run the following PowerShell commands (or some similar variation):

    Import-Module SkypeOnlineConnector
    $sfboSession = New-CsOnlineSession -UserName "[email protected]"
    Import-PSSession $sfboSession

    If you don’t have Skype for Business On-Premises, it should just work. If you DO have it and set up hybrid, you’ll probably get this error:

    Unable to discover PowerShell endpoint URI.
    At C:\Program Files\Common Files\Skype for Business
    Online\Modules\SkypeOnlineConnector\SkypeOnlineConnectorStartup.psm1:155 char:9
    +         throw $resources.DiscoveringEndpointFail
    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : OperationStopped: (Unable to disco...l endpoint URI.:String) [], RuntimeException
        + FullyQualifiedErrorId : Unable to discover PowerShell endpoint URI.

    Or, you might get this error if you managed to get the interactive logon to pop up first and then entered your credentials there:

    Get-CsOnlinePowerShellAccessToken : One or more errors occurred.
    At C:\Program Files\Common Files\Skype for Business
    Online\Modules\SkypeOnlineConnector\SkypeOnlineConnectorStartup.psm1:214 char:28
    +             $accessToken = Get-CsOnlinePowerShellAccessToken @params
    +                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Get-CsOnlinePowerShellAccessToken], AggregateException
        + FullyQualifiedErrorId : System.AggregateException,Microsoft.Rtc.Management.OnlineConnector.GetPowerShellAccessTo
       ken

    There’s a huge amount of potential fixes offered, but for me it was one simple switch, which I found thanks to enterinit.com – use the -OverrideAdminDomain switch.

    Import-Module SkypeOnlineConnector
    $sfboSession = New-CsOnlineSession -UserName "[email protected]" -OverrideAdminDomain “yourtennant.onmicrosoft.com”
    Import-PSSession $sfboSession

    Really easy fix, but the errors really don’t make it sound like this could be your problem. Now the next time I try to connect, I’ll re-read my own blog post to remind me of this switch :)

    Note: I had to connect to this to make a Microsoft Teams change!

    How To Check What Files Are In Use On A Remote Windows Computer

    This one had me stumped for a while, and I even asked on Twitter with a large amount of replies (thanks everyone who did!) but none that I could get to work, or that weren’t overly complicated requiring the compiling of code.

    It’s easy locally to find out what files are open, and here’s a great article covering several free ways: https://www.winhelponline.com/blog/find-process-locked-file-openfiles-utility/

    None of those worked remotely for me in a Windows 10 environment – but I thought Handle from the SysInternals Suite would be the best bet. Running locally, it did exactly what I wanted – a giant list of every file open, and say what process had it open (like WinWord.exe).

    Using PSExec with Handle however, causes it to forever wait for something. On the remote PC, it definitely launches handle.exe and handle64.exe, but they have no activity. I thought it might be the EULA prompt getting stuck somewhere, but there’s a registry setting that will autoaccept that prompt, and putting that in place didn’t help (but I did check locally and it was skipping the EULA agree prompt. Thanks to this blog post explaining the reg key required https://peter.hahndorf.eu/blog/post/2010/03/07/WorkAroundSysinternalsLicensePopups which was:

    reg.exe ADD HKCU\Software\Sysinternals /v EulaAccepted /t REG_DWORD /d 1 /f

    I added this to the remote machine under both the user logged on to the remote device, and the user I was connecting as, with no luck.

    After a bunch of Googling and trying solutions, I ended up finding this thread on stackoverflow. One of the answers with 0 votes (which can be easily overlooked) was a PowerShell script, invoking the command remotely, from a user called A.D – thank you A.D!

    I’ve barely modified it for my purposes, but if this helps you please go vote his post up on stackoverflow (I did but don’t have enough rep for it to show):

    $computerName = 'computername'
     $stringtoCheck = 'test' # String you want to search for, can be blank by removing text between '' quotes
     $pathtoHandle = 'c:\temp\handle.exe' #location of handle.exe on the remote server.
     Invoke-command -ComputerName $computerName -Scriptblock {
         param(
         [string]$handles,
         [string]$stringToCheck
         )
          "$handles /accepteula $stringToCheck" | Invoke-Expression 
         } -ArgumentList $pathtoHandle,$stringtoCheck

    The script requires handle.exe to be on the remote computer under C:\Temp, and that of course you have admin rights to the remote PC with the account this script is being run. Beyond that, it’ll show back all open files that match the variable set in $stringtocheck across any of the results – it could be the path, the process that has the file open etc.

    Why would you want to do this remotely at all? You might be troubleshooting something to do with open files and not want to interrupt the user. You might have a reason to see what files the user has open, or maybe it’s a locked PC and the user left.

    Hope this helps others as it was a much harder task to accomplish than I assumed.

    Migrate a Single Mailbox Out of a Exchange Online Migration Batch

    A few posts on this since it’s what I’m working on :)

    It is possible to sync all your mailboxes from Exchange On-Prem to Exchange Online as a single batch, and then complete individual items – but it’s not obvious that this is even possible.

    Normally if you start a migration, you can choose multiple mailboxes or use a CSV file to specify which accounts to start migrating – while specifying the option to manually complete the batch, so the actual migration happens when you want it to:

    The problem is, once you’ve fixed any problems that arise and mailboxes are in a ‘synced’ state, there’s no visible way to complete a single mailbox – just the whole batch. That may not be what you want to do. You could work out a way to create a separate batch for every single mailbox you’re migrating, but there’s also a way to complete one mailbox at a time.

    In PowerShell, once you’ve connected to Exchange Online, you can run the a command to see all the mailboxes syncing, and their status:

    Get-MoveRequest

    If a mailbox is ready to be finalised, it should have the status of ‘Synced’. This is different to the status of ‘Completed’, which occurs once the mailbox has been fully migrated across.

    To trigger the completion of a single mailbox in a batch, use this PowerShell command:

    Set-MoveRequest -Identity “mailbox name” -CompleteAfter 1

    The mailbox will then do it’s final syncing and complete, without affecting the other jobs in the same batch. The -CompleteAfter parameter is supposed to set the delay before the request is completed in date/time format, but using the value ‘1’ seems to immediately trigger this.

    Now you can do a single batch job, and selectively complete mailboxes as you choose – easy! 

    (Note that there was an old method of doing the above by setting the variable SuspendWhenReadytoComplete to $false which no longer works)

    Deploying a Locked Down Start Menu in Windows 10

    The tiles in Windows 10’s Start Menu can be rather messy. By default, you get a lot – and they may be things you don’t want there such as News, Sports, Photos, Microsoft Store etc.

    Since Windows 10 1607, there’s been a way to control this. Customize Windows 10 Start and taskbar with Group Policy covers how to do this, but there’s some errors and links that don’t work, so I thought it was worth giving a quick overview on how to do this.

    Keep in mind that this process locks down the Start Menu tiles completely, users won’t be able to add, remove or change anything to do with tiles.

    The first step is to configure the Start Menu tiles how you want them on a computer. You can add, remove, move, resize etc until you’re happy with how it looks.

    Once that’s done, you’ll need to export the layout to an XML file. Easily done by opening PowerShell and running Export-StartLayout. This needs the -Path switch, e.g. Export-StartLayout -Path “C:\temp\startmenu.xml”

    Copy the resulting startmenu.xml file into a central location that clients will be able to access, or copy it out to each machine through Group Policy Preferences. This XML file will be called in the Group Policy setting “Start Layout”.

    The Group Policy setting called “Start Layout” lives in User Configuration or Computer Configuration > Policies> > Administrative Templates >Start Menu and Taskbar. You’ll probably want this at the user level rather than the computer level, but it depends at what layer you want this locked down at. 

    If you can’t see this policy at all, then you may need to update your Group Policy templates. Each time a new version of Windows 10 comes out, there’s usually new or updated Group Policies to use. There’s a good step-by-step here if you need help – I’d recommend downloading the templates that match the latest version of Windows 10 you’re managing.

    Start Layout in Group Policy

    For this policy, you’ll be setting the radio button to Enabled, and setting the Start Layout File value to the path of the XML file that you copied out or placed centrally.

    Start Layout Settings

    Once that is done, the Group Policy object containing this setting needs to be pointed at the users or computers you want it to apply to, just like any other Group Policy.

    The end result is the client then having the same Start Menu tiles configured in the XML file.

    You may find that some of the tiles are missing. I’ve seen this happen when the shortcut the XML points to isn’t in the location expected. Here’s an example XML file with just one tile configured for Notepad:

    <LayoutModificationTemplate xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout" xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout" Version="1" xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification">
     <LayoutOptions StartTileGroupCellWidth="6" />
     <DefaultLayoutOverride>
     <StartLayoutCollection>
     <defaultlayout:StartLayout GroupCellWidth="6">
     <start:Group Name="">
     <start:DesktopApplicationTile Size="2x2" Column="0" Row="0" DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\Notepad.lnk" />
     </start:Group>
     </defaultlayout:StartLayout>
     </StartLayoutCollection>
     </DefaultLayoutOverride>
    </LayoutModificationTemplate>

    When a Tile is added to the Start Menu, if it doesn’t exist already, it will create a .LNK file and uses that for the tile. You may need to copy these off the computer you created the tiles on the in first place too, and copy them out to the same path on the computers you’re pushing this setting to.

    You can also manually update or change the XML file yourself, which can sometimes be easier than going through the whole export process again.

    One last thought I have on this, is that you can have multiple XML files going to different computers or users based on their requirements – but don’t over complicate things or you’ll be constantly managing tiles!

    Exchange Online and Exchange On-Premises Security Groups

    I had a requirement come up where I needed a list of Exchange Online users as well as Exchange On-Premises users. If you’re hybrid or going through a long migration, there’s many scenarios where you want certain products or settings applied against the users and accounts based on where their mailbox is.

    You could maintain that manually, but why do that when you can automate it!

    Thankfully there’s two easy commands that will return accounts based on where they are – get-mailbox and get-remotemailbox. As long as you’re in Exchange Hybrid mode, you can run both of these commands against your local Exchange server.

    Here’s the quick script I wrote up:

    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchange server name/Powershell -Authentication Kerberos
    Import-PSSession $session
    $remoteusers = Get-RemoteMailbox -resultsize unlimited
    $onpremusers = Get-mailbox -resultsize unlimited
    $remotegroup = "Exchange Online Users"
    $onpremgroup = "Exchange OnPrem Users"
    
    Get-ADGroup $remotegroup | Set-ADObject -clear member
    Get-ADGroup $onpremgroup | Set-ADObject -clear member
    
    foreach ($user in $remoteusers){
    Add-ADGroupMember -Identity $remotegroup -Members $user.SamAccountName
    }
    
    foreach ($user in $onpremusers){
    Add-ADGroupMember -Identity $onpremgroup -Members $user.SamAccountName
    }
    
    Remove-PSSession $Session
    
    

    All I’m doing here is getting the two user types and putting them into variables – $remoteusers and $onpremusers. I’ve also used the group names as variables, so you can create whatever AD groups you like and put them into the $remotegroup and $onpremgroup variables.

    Also I’m clearing out all members of the group before adding users, which means if someone gets migrated or leaves, they’ll be cleaned up from the old group properly.

    Finally I’m going through the two users lists and using ‘foreach’ to add each user to the relevant group, using the SamAccountName value of each result to identify them.

    It’s only a basic script, but you can easily add extra filters to the Get-Mailbox and Get-RemoteMailbox commands to do things such as only getting users from certain OUs.

    This was tested on Exchange 2010, so if you get different results on Exchange 2013 or 2016 please let me know.