Development Project: PowerShIM – run PowerShell on remote servers using Skype for Business Instant Messaging
This is a little project I did just to see if it possible (and thanks to Skype for Business MVP Tom Arbuthnot for the idea!). Can I use Skype for Business to enable administrators to execute PowerShell remotely simply by sending Instant Messages, and get response data back.
Is this useful? Well, it could be – you could run this on all FE Servers and have a quick way to interrogate them or run PowerShell commands. And of course, the administrator could be on another network or on the other side of the world – this will work with federation and won’t require complicated holes in firewalls for remote access.
I’m releasing it for free to the community – it’s pretty basic and there are probably some bugs .. but if you’d like to try it out, feel free. I’m also open-sourcing the code on GitHub so you can take it and improve it.
How?
A UCMA application which exposes a Skype for Business endpoint and listens for incoming instant messages. When it receives a message, it validates that the sender is on the approved whitelist of senders (my minimal security checking to opening up a root PowerShell instance to the world!) and then executes the command in a PowerShell run-space. Any output is fed back to the user as another instant message.
TL;DR – Show Me

Getting the Management Store Replication Status of a remote Skype for Business server by sending it an Instant Message!
Installation Steps
OK, so installation is a little bit .. involved 🙂 Â You can only run this on a Trusted Application Server or Front End server – essentially anywhere you can run UCMA applications. Firstly, download the zipped program files and extract to a location of your choice.
Then, open the Skype for Business Management Shell on that server. Create the Trusted Application entry:
New-CsTrustedApplication -ApplicationId impowershell -Port 6000 -TrustedApplicationPoolFqdn yourPoolFQDN.domain.com
Make sure you use a port number that’s not in use, and substitute the TrustedApplicationPoolFqdn with the actual Trusted Application Pool FQDN. (If you’re not sure about the port number, run get-cstrustedapplication to get a list of existing applications and their port numbers.)
Now, create the endpoint which you will interact with:
New-CsTrustedApplicationEndpoint -TrustedApplicationPoolFqdn yourPoolFQDN.domain.com -ApplicationId impowershell -SipAddress “sip:powershim-app1@domain.com” -DisplayName “PowerShIM App 1”
Again, replace the TrustedApplicationPoolFqdn value. You can also specify your own values for the SIP address and display name to enable you to identify which server you’ll be talking to.
Lync 2013 Users…
If you’re still using Lync 2013 or want to install this on a Lync 2013 application server, you will need to perform an additional step. The code has been compiled against the latest UCMA SDK, which isn’t recognised by Lync 2013. However, a quick tweak to the config file can be used to force the code to use UCMA 4 again. Navigate to the code folder and use Notepad to open IMPowershell.exe.config.  Immediately after the closing </startup> tag, add this section:
1 2 3 4 5 6 7 8 | <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Microsoft.Rtc.Collaboration" publicKeyToken="31bf3856ad364e35" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="5.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> |
Trusted Users
I’ve added a check when accepting incoming messages, so that only white-listed SIP addresses will be processed. That white-list is maintained in the file authorisedusers.txt in the code folder, so go there now and add your SIP address, otherwise you’ll be ignored.
When you’re ready, run IMPowershell.exe as an Administrator. A console window should open, and you should see Lync Server Ready. That means everything is set up OK. If not, check the code folder – there will be a log.txt file, errors and other info are logged there.
Trying It Out
Find the SIP address you specified earlier and open a new conversation window. Send a PowerShell command as an instant message. If everything has gone well, you’ll see the output:
Good to Know #1
You can only send one line – what you send when you press Enter is what gets executed. However, that doesn’t stop you piping commands, as in this example to find out when the machine was last restarted, and by whom:
Good To Know #2
Because lots of the commands might be to do with Skype for Business, I always automatically import the Lync module prior to running commands. So you can do this:
Run as a Service
The code is structured so that you can also run it as a Windows Service. To do this you need to add it using SC CREATE and point the binPath to the same .exe file you ran above. The only gotcha is that when you do this the code runs in a different folder, so it can’t find the authorised users text file. To fix this, edit the IMPowershell.exe.config file – you’ll notice there’s a setting for the file location. You can change this to be the actual path to the file, not just the relative location. This will fix the service.
Get the Code, Fix the Bugs
This is very much a “try it and see” development project. I’d be interested to hear about things not working, or bugs – but please don’t expect much response from me. If you’re a developer, you’d be much better off grabbing the code yourself and fixing it or changing it to work for you.
Wow…neat idea. I have a few scheduled tasks on our S4B FE that run nightly, but sometimes I kick them off manually. The tasks just execute different ps1 scripts that I created for UM voicemail and disabling S4B users. Could I execute these ps1 files via IM you think using your method?
run IMPowershell.exe as an Administrator. errors :
———————————————————————————————-
2016-09-28 16:22:26,490 INFO [1] IMPowershell.LyncServer – Starting Collaboration Platform
2016-09-28 16:22:26,582 INFO [1] IMPowershell.Program – Started
2016-09-28 16:22:27,081 ERROR [5] IMPowershell.LyncServer – Error establishing collaboration platform: Microsoft.Rtc.Collaboration.ProvisioningFailureException:One or more values in the configured settings are invalid or unusable. Check inner exception and logs for more details. —> Microsoft.Rtc.Internal.ServerConfiguration.SettingsInitializationException: æªè½åå§å设置å è£ ã
计ç®æºä¸æªå®è£ ExternalServer æå¡ã
å¨ Microsoft.Rtc.Internal.ServerConfiguration.UCSettings.InitConsumerWithRole(RoleName role)
å¨ Microsoft.Rtc.Internal.ServerConfiguration.UCSettings..ctor(String applicationId, SettingsWrapperOptions options)
å¨ Microsoft.Rtc.Internal.ServerConfiguration.UCSettings.Get(String applicationId, SettingsWrapperOptions options)
å¨ Microsoft.Rtc.Collaboration.ProvisioningSourceImpl.GetInitialPlatformData()
— End of inner exception stack trace —
å¨ Microsoft.Rtc.Signaling.SipAsyncResult`1.ThrowIfFailed()
å¨ Microsoft.Rtc.Signaling.Helper.EndAsyncOperation[T](Object owner, IAsyncResult result)
å¨ System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
— å¼åå¼å¸¸çä¸ä¸ä½ç½®ä¸å æ è·è¸ªçæ«å°¾ —
å¨ System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
å¨ System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
å¨ IMPowershell.LyncServer.d__11.MoveNext()
Detected å¨ System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
å¨ System.Environment.get_StackTrace()
å¨ Microsoft.Rtc.Collaboration.ProvisioningFailureException..ctor(String message, Exception innerException, ProvisioningFailureReason failureReason)
å¨ Microsoft.Rtc.Collaboration.ProvisioningSourceImpl.GetInitialPlatformData()
å¨ Microsoft.Rtc.Collaboration.ProvisioningSourceGetInitialPlatformDataAsyncResult.ProcessCoreHelper()
å¨ Microsoft.Rtc.Collaboration.SipCollaborationAsyncResult.ProcessCore()
å¨ Microsoft.Rtc.Signaling.AsyncWorkitemQueue.ProcessItems()
å¨ Microsoft.Rtc.Signaling.QueueWorkItemState.ExecuteWrappedMethod(WaitCallback method, Object state)
å¨ System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
å¨ System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
å¨ System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
å¨ System.Threading.ThreadPoolWorkQueue.Dispatch()
FailureReason = 2