New Podcast: UC Inside Track Takes a Look at Microsoft UC Stack

November 22nd, 2017 No comments

Earlier this year, the podcast I had been involved in since its inception, The UC Architects, ended its five-year run. It was a lot of fun, but the format was difficult to maintain. So, I’ve started a new podcast dealing with the Microsoft Unified Communications (UC) stack, with just a single guest on each episode. Guests will be tech luminaries such as fellow MVPs, MCMs/MCSMs, and/or Microsoft staff. The podcast is designed to deliver regular insight into the Microsoft UC market as an easy-to-consume audio file. Each episode will be in a shorter format than the UCA was, making it easier to listen to while driving to work or doing other tasks. We’ll tackle fewer topics, but each in further depth.

Listeners can listen to the podcasts in any of the following ways:
– Subscribe to the UC Inside Track podcast series on iTunes or via your favorite RSS client.
– Listen to the podcast directly via the link below.

In the first podcast, I’m joined by Jonathan McKinney (@ucomsgeek), MVP and MCM, to discuss the recently released Skype for Business to Teams capabilities roadmap. Both Jonathan and I are on the Microsoft Elite Teams for both Skype for Business and Microsoft Teams, and have collaborated for many years. The exchange is lively and informative. The recording was made available to download from iTunes on Friday, November 17.

Episode 1: http://www.voss-solutions.com/media/podcast/podcast_001.mp3

Look for more episodes at least monthly. In episode 2, I’ll be joined by fellow MVP and MCM Tom Arbuthnot (@tomarbuthnot).

One-Liner: Get Your Office 365 Tenant ID

November 8th, 2017 No comments

Office 365 logoThere are occasions that you’ll need your Office 365 tenant ID. The tenant ID is just a GUID assigned to your tenant. You can look it up in the Office 365 admin portal by peeking under Admin>Admin Centers>Azure AD>Azure Active Directory>Properties, and you’ll see the tenant ID in the ‘Directory ID’ field. That’s quite a few clicks, AND you have to log in to the Office 365 portal. Over time, there have been other places in the Office 365 portal where you can find it as well. All of them requiring a handful of clicks.

If you’re logged into your tenant via the SkypeOnlineConnector PowerShell module, you can use the following to get your tenant ID:

Get-CsTenant | Select-Object DisplayName, TenantID

Note, if you’re not logged in via the SkypeOnlineConnector, you can run the following first after installing the SkypeOnlineConnector:

Import-Module -Name SkypeOnlineConnector
$session = New-CsOnlineSession -Credential $(Get-Credential)
Import-PSSession -Session $session

Just like the portal method, this requires you to be logged in. There’s a similar method if you’re connected to Microsoft Azure Active Directory Module for Windows PowerShell using Get-MsolPartnerContract, but with the same limitations. You get the idea. But sometimes you just need the tenant ID without having to login to anything. Well, a PowerShell one-liner to the rescue! Just change the ‘mycompany.onmicrosoft.com’ to your Office 365 domain name in the line below and run it in PowerShell:

(Invoke-WebRequest -Uri 'https://login.windows.net/mycompany.onmicrosoft.com/.well-known/openid-configuration' | ConvertFrom-Json).authorization_endpoint.Split('/')[3]

If you look at that one-liner, you see that we’re merely invoking a web request to a specific URI, converting the Json format that it returns, and then grabbing a bit of the resulting Uri.

If you want it to be a little more flexible, we can adjust the code to prompt for a domain name, in case you want to use it in various scripts, as well as suppress the verbose output:

(Invoke-WebRequest -Uri "https://login.windows.net/$(Read-Host -Prompt 'enter domain name')/.well-known/openid-configuration" -Verbose:$false | ConvertFrom-Json).authorization_endpoint.Split('/')[3]

Of course, us consultant types can turn that into a function and toss it into our PowerShell profiles so that it’s always available. This method works with either your default onmicrosoft.com or your primary vanity domain name. I don’t have a tenant with multiple domain names to test with, but I surmise that it works the same.

While I had this post in draft, my buddy Tony Redmond (@12knocksinna) tweeted a link to an article that shows you how to retrieve the tenant ID using a web browser. From a web browser, you can also get the information by going to the same URL as determined above:

https://login.windows.net/mycompany.onmicrosoft.com/.well-known/openid-configuration

In the results, look for a line that begins with ‘authorization_endpoint’ (usually the first line), and you’ll see your tenant ID GUID in the URL on that line.

As you can see, there are multiple ways to get the tenant ID. Some require PowerShell, some don’t. Some require you to login, some don’t.

 

Review: BounSky – Configure Multiple Accounts to Easily Switch Between With Your Skype for Business Client!

October 23rd, 2017 No comments

Description

Anyone working in Skype for Business consulting or support knows what a pain it is to test various accounts in an environment. Whether it’s testing an account in a new environment you’ve deployed, or troubleshooting an issue for a user, the process isn’t as easy as it could be. The Lync and Skype for Business clients only allow you to configure a single account. Well, once again, the community comes to the rescue!

Office Servers and Services (Skype for Business) MVP Greig Sheridan (@GreigInSydney) has come up with a solution with his clever BounSky application. The successor to ‘Profiles for Lync’, BounSky allows you to configure up to, wait for it, EIGHTY different Lync and Skype for Business accounts, and switch between them at the click of the mouse or hotkey combo. Each account can be configured with the typical sign-in address, user name, password, and alias. But each can also be manually configured to use specified internal and external server names, which is key in testing out scenarios where DNS is either not yet configured, or you’re attempting to bypass current configuration. Passwords are stored encrypted in the configuration file. The configuration can be exported and imported, which makes moving between machines or reloading workstations less troublesome.

BounSky menu

Once configured a simple click on the taskbar/system tray icon brings up the menu and allows you to quickly click on a button next to each listed account. Presto, the client signs out of the current account and signs into the chosen account.

A nice feature is the new Auto-Home feature, which will automatically log you back into a default account after a configured number of hours and minutes. This is great in case you forget that you’re logged into a test account. A little stopwatch icon shows in the lower left corner to show the timer, and allows you to disable/suspend the Auto-Home feature. Click the image below for a view of the various options available.

BounSky options screen

BounSky also has the ability to be utilized to change sites by a command line interface.

Greig has informative user guide about the product on the BounSky website that breaks down every feature in detail. There’s also great troubleshooting and FAQ sections, although I don’t think I’ve ever had to use them. The tool just plainly works.

Installation

Download the MSI installer and run through it. Once launched, you can set the accounts and various application settings. Click the image below to see what the account setup screen looks like.

BounSky Account Setup screen

Conclusion

This is a phenomenal solution for those who must log-in to various Lync/SfB accounts. I use it often on deployments to test accounts in new pools, or those in Office 365 for hybrid scenarios. I also have work and personal tenants that I switch between. I can’t recommend this solution enough. Greig’s done an awesome job at filling a gap in the Skype for Business client space.

Review: UC Extend – Set Your Skype for Business Presence Based On Time of Day!

May 1st, 2017 No comments

Description

A common request from Lync and Skype for Business users over the years is to be able to set Lync/Skype for Business presence based on rules. And a common rule is time. So, set my presence to ‘away’ at 5pm every weekday. Well, as is usually the case, the community was listening, and Andrew Morpeth (@AndrewMorpeth), fellow Skype for Business MVP, released a free solution called UC Extend.

UC Extend allows you to set various time of day presence and personal note changes. For example, you can set a presence of ‘Off work’ to start at 5:30pm until 8:00am. And the personal note can be something like ‘It’s currently outside my normal work hours so I may not respond’. UC Extend forces the configured presence and personal note during this time. So, even if you’re working on your computer, which would normally cause a presence of ‘available’, UC Extend will keep it as ‘Off work’. There are 4 standard ‘rules’ that can be enabled and configured, and they apply to every day. There is also a weekend rule that matches based on the day. So, if your weekend is Saturday and Sunday, from Midnight Friday till midnight Sunday, the configured rule will apply, and takes priority over the other rules. See the screenshot below to see how the application lays out the rules and their settings.

UC Extend has several other options as well, including forcing one UI (Skype for Business/Lync), and automatic ‘away’ settings. Andrew has done some work around also adding configuration of unanswered calls, but those features are not yet available. The right-click context menu for the application also supports custom options and custom URLs that are configured in the applications XML file. I did play around with those, but haven’t really found a need for them yet. Other options include the ability to include custom option in the Lync/Skype for Business context menu, which could be nicer. This includes custom support URLs, as well as the ability to launch an application. UC Extend also supports configuring registry keys in the XML, and forcing a value for those keys, such as when a change is detected. Andrew’s site describes these features further, and the XML file has some decent comment notes.

UC Extend will trigger a toast notification and system sound whenever it makes presence changes. For me, that’s a good ‘reminder’ that the working day is over.

Installation

Download the application from the TechNet Gallery. Run through the MSI installer, and you’re up and running. You can right-click on the ‘UC’ system tray icon to adjust the settings.

Conclusion

The application was released a while ago, and some of the dialogs mentions Lync instead of Skype for Business. The bottom of the settings dialog is an example of this. It doesn’t detect the sign-in status of Skype for Business in that location. But the application works great for Skype for Business clients. I really like the solution, and continue to use it to this day. My only real request, other than updating some of the dialogs to support Skype for Business, is to store the original personal note, so that when it’s outside of any of the configured rules, it can restore that. Or, and option to pick the original personal note instead of forcing a configured personal note. Certainly not a show stopper for me – just a wish. Maybe Andrew will get around to finishing the ‘unanswered calls’ config. Still, a cool solution that fills a known gap.

Review: Skyue – Set Your Phillips Hue Lights to Match Your Skype for Business Presence!

April 1st, 2017 No comments

Description

A year or so ago, when I began some renovation projects, I started to really get into home automation. I’ve now got two Nest thermostats, probably a dozen Nest Protect devices, some Next cameras, dozens of Phillips Hue lights, and a bunch of SmartThings sensors, smart plugs, and more. I’m also utilizing things like 7 Amazon Alexa devices, Siri, and IFTTT and Stringify to help tie things together. They really all work great, with the exception of the Ring video doorbell, which, IMHO, is a piece of garbage –

For quite some time I’ve played with some of the presence lights by vendors such as Embrava’s Blynclight and Kuando’s Busylight family. They work really well at letting others see your presence before they interrupt you. I’ve used them both at customer locations (‘cube farms’) and in my home office. The problem in the home office is that I wanted the light to be outside of my office door, so that family members could see my presence if my door was closed. That required a long USB extension cable, and wasn’t the cleanest looking solution. Then one day, I was playing with some Hue bulbs and Alexa, when it dawned on me that it would be cool to change one or more Hue bulbs to match my presence. So I reached out to Tom Morgan (@TomMorgan), Skype for Business MVP, ace developer, and former coworker, with my idea. Not long after that, he introduced Skyue. Skyue is a client side system tray application that controls Hue lights, and sets them to your presence. Brilliant!

I set this up to utilize a light on the main floor, near the stairs. My office is on the second floor, so the light is visible before people come up the stairs. While my family doesn’t know what all the various presence colors mean, they do know that red (busy/on a call/in a conference) and purple (DND) mean that you approach my office with caution. My grandson also knows that it means not to run around the house like a banshee, because Papa is probably on a call (hey – I’m no presence liar!). The app keeps the bulbs in sync, and there is really no delay between a change in presence and the bulb color being updated.

Installation

Pretty easy, really. Download the tool and run it. It will prompt you to press the button on your Hue hub. Once that’s done, click the button in the app acknowledging that you pressed it. Select the Hue light(s) that you’d like to control. Only the color ambience lights should be visible in the pick list. White ambience lights are not – for good reason. You can also set the level of brightness for the lights. This was something that I asked for because a Hue light is pretty bright. So, setting to 20% was a perfect level to be seen but not overbearing. Once you hit ‘Save’, you’re off and running. You can also adjust the settings by right clicking on the little floor lamp icon in the system tray.

Coming soon

According to the website, Tom is looking at adding Contacts so you can show their presence, as well as notifications for incoming IMs, and incoming calls.

Conclusion

I’ve been running this for many months now, and it’s been rock solid. If you’re looking to have a quality solution to leverage existing Hue lights and Skype for Business, Tom’s cool utility should fill the requirement. Highly recommended!

Function: Test-InvalidCerts – Ensuring Certificates Are In The Correct Certificate Store

March 17th, 2017 8 comments

I learned some very valuable lessons on my first Lync Server deployment. If Lync doesn’t work, it’s probably certificate related. That’s still the case with Skype for Business. Simple certificate issues can cause all kinds of problems. That’s why I’ve written about them in Lync Users Can’t Download Address Book if Certificate Uses CNG and One Liner: Add Trusted Root Cert Authorities to Edge Servers (among others). Today I again saw another issue around McAfee solutions and Front End services not starting (see An intermediate certificate is installed under “Trusted Root Certification Authorities” for more info). Turns out some certificates are in the wrong certificate store. An example would be an intermediate certificate improperly placed in the ‘Trusted Root Certification Authorities’. The solution is to just move the cert into the correct store. But rather than manually looking at each one, surely there is a way to check them all and move them to the right store! Well, Shirley (see what I did there?), there is.

Here is a simple function that checks both the intermediate and trusted root stores for certificates that should be in the opposite store. If it finds them, it will move them. Run this in an elevated PowerShell session.

function Test-InvalidCert {
  <#
  .SYNOPSIS
    Checks root and intermediate certificate stores for certificates in the wrong store & moves them to the correct store.

  .DESCRIPTION
    Checks root and intermediate certificate stores for certificates in the wrong store & moves them to the correct store. Certificates in the wrong store can be very problematic for Lync Server and Skype for Business Server.

  .NOTES
    Version               : 1.2
    Wish list             : 
    Rights Required       : Local administrator on server
    Sched Task Required   : No
    Lync/Skype4B Version  : N/A
    Author/Copyright      : © Pat Richard, Office Servers and Services (Skype for Business) MVP - All Rights Reserved
    Email/Blog/Twitter    : pat@innervation.com  https://ucunleashed.com  @patrichard
    Donations             : https://www.paypal.me/PatRichard
    Dedicated Post        : https://ucunleashed.com/3903
    Disclaimer            : You running this script means you won't blame author(s) if this breaks your stuff. This script is
                            provided AS IS without warranty of any kind. Author(s) disclaim all implied warranties including,
                            without limitation, any implied warranties of merchantability or of fitness for a particular
                            purpose. The entire risk arising out of the use or performance of the sample scripts and
                            documentation remains with you. In no event shall author(s) be liable for any damages whatsoever
                            (including, without limitation, damages for loss of business profits, business interruption,
                            loss of business information, or other pecuniary loss) arising out of the use of or inability
                            to use the script or documentation. Neither this script, nor any part of it other than those
                            parts that are explicitly copied from others, may be republished without author(s) express written 
                            permission.
    Acknowledgements      :
    Assumptions           : ExecutionPolicy of AllSigned (recommended), RemoteSigned, or Unrestricted (not recommended)
    Limitations           :
    Known issues          : None yet, but I'm sure you'll find some!

  .LINK
    
Function: Test-InvalidCerts – Ensuring Certificates Are In The Correct Certificate Store
.EXAMPLE .\Test-InvalidCerts.ps1 Description ----------- Checks root and intermediate store for certs in the wrong store & moves them to the proper store. .INPUTS None. You cannot pipe objects to this script. #> [CmdletBinding(SupportsPaging)] param( ) # end of param block process{ Write-Verbose -Message 'Checking for improper certs in root store' $InvalidCertsInRoot = Get-Childitem -Path cert:\LocalMachine\root -Recurse | Where-Object {$_.Issuer -ne $_.Subject} if ($InvalidCertsInRoot){ Write-Verbose -Message ('{0} invalid certificates detected in Root Certificate Store' -f $InvalidCertsInRoot.count) ForEach ($cert in $InvalidCertsInRoot){ Write-Verbose -Message ('Moving `"{0}`" to intermediate certificate store' -f $cert.subject) Move-Item -Path $cert.PSPath -Destination Cert:\LocalMachine\CA } }else{ Write-Verbose -Message 'No invalid certs found in Root Certificate Store' } $InvalidCertsInInt = Get-Childitem -Path cert:\LocalMachine\Ca -Recurse | Where-Object {$_.Issuer -eq $_.Subject} if ($InvalidCertsInInt){ Write-Verbose -Message ('{0} invalid certificates detected in Intermediate Certificate Store' -f $InvalidCertsInInt.count) ForEach ($cert in $InvalidCertsInInt){ Write-Verbose -Message ('Moving `"{0}`" to root certificate store' -f $cert.subject) Move-Item -Path $cert.PSPath -Destination Cert:\LocalMachine\Root } }else{ Write-Verbose -Message 'No invalid certs found in Intermediate Certificate Store' } } } # end function Test-InvalidCert

All output is verbose, so if you want to see what it’s doing, run the function with -Verbose.

Donations

I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.

Categories: PowerShell Tags:

Using PowerShell to Add “Open PowerShell Here” and “Open Command Prompt Here” to Explorer Context Menus

February 17th, 2017 1 comment

Description

I recently reloaded my primary workstation. When I do that, I run my PowerShell profile file manually once, and it configures quite a few things, including dot sourcing that same file into the main profile file, which it also creates. When I was all done, I realized I didn’t have the “Open PowerShell Here” options when right clicking in Windows Explorer. Being old and forgetful, I did some searching and was quite surprised (and disappointed) that every method for setting this was either using an .inf file, a .reg file, or manually editing the registry. What a travesty that no one was showing how to do it via PowerShell. So a bottle of Mountain Dew Throwback and some Pink Floyd was all I needed.

The right-click options can be visible in three different places. The first is when you right-click a folder. Second is when you right-click a root drive. And the third is when you right-click the background in Explorer. The method I’ll be showing here will include setting the option for all three. Additionally, I’ll show how to add an option to open an elevated PowerShell session, as well as a command prompt. When finished, you’ll end up with something like this:

All options are configured in the HKEY_CLASSES_ROOT hive of the registry. The folder level option resides under \Directory\shell; the root drive option resides under \Drive\shell; and the Explorer background option resides under \Directory\Background\shell. For each, we create a new key with a unique name for each of the options (PowerShell, PowerShell elevated, cmd prompt), some values for the display text and icon, and a sub key with a value that shows what commands will run when executed. This is easily accomplished using the New-Item and New-ItemProperty cmdlets in an elevated PowerShell session.

Add “Open PowerShell Here”

#folders
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere" -Value "Open PowerShell Here" -Force | Out-Null
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere\command" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location '%V'" -Force | Out-Null
New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe" -Force | Out-Null
#background
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere" -Value "Open PowerShell Here" -Force | Out-Null
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere\command" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location '%V'" -Force | Out-Null
New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe" -Force | Out-Null
#root drives
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere" -Value "Open PowerShell Here" -Force | Out-Null
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere\command" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location '%V'" -Force | Out-Null
New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe" -Force | Out-Null

Add “Open PowerShell Here (Admin)”

#folders
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin" -Value "Open PowerShell Here (Admin)" -Force | Out-Null
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin\command" -Value "PowerShell -Command `"Start-Process $env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe`" -verb runAs -ArgumentList '-NoExit','Set-Location','''%L'''" -Force | Out-Null
New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe,1" -Force | Out-Null
#background
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin" -Value "Open PowerShell Here (Admin)" -Force | Out-Null
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin\command" -Value "PowerShell -Command `"Start-Process $env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe`" -verb runAs -ArgumentList '-NoExit','Set-Location','''%V'''" -Force | Out-Null
New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe,1" -Force | Out-Null
#root drives
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin" -Value "Open PowerShell Here (Admin)" -Force | Out-Null
New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin\command" -Value "PowerShell -Command `"Start-Process $env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe`" -verb runAs -ArgumentList '-NoExit','Set-Location','''%L'''" -Force | Out-Null
New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe,1" -Force | Out-Null

Add “Open Command Prompt Here”

#folders
New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdHere" -Value "Open Command Prompt Here" -ItemType string -Force | Out-Null
New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdHere\command" -Value 'cmd.exe /k pushd %L' -ItemType string -Force | Out-Null
New-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdHere" -Name "Icon" -Value "C:\Windows\System32\cmd.exe,0" -Type string -Force | Out-Null
#background
New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdHere" -Value "Open Command Prompt Here" -ItemType string -Force | Out-Null
New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdHere\command" -Value 'cmd.exe /k pushd %L' -ItemType string -Force | Out-Null
New-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdHere" -Name "Icon" -Value "C:\Windows\System32\cmd.exe,0" -Type string -Force | Out-Null
#root drives
New-Item -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdHere" -Value "Open Command Prompt Here" -ItemType string -Force | Out-Null
New-Item -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdHere\command" -Value 'cmd.exe /k pushd %L' -ItemType string -Force | Out-Null
New-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdHere" -Name "Icon" -Value "C:\Windows\System32\cmd.exe,0" -Type string -Force | Out-Null

Now we can put that all together, and add some checks to ensure the settings are not already configured, to avoid errors.

# folders
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere")){
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere" -Value "Open PowerShell Here" -Force | Out-Null
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere\command" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location '%V'" -Force | Out-Null
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe" -Force | Out-Null
}
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin")){
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin" -Value "Open PowerShell Here (Admin)" -Force | Out-Null
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin\command" -Value "PowerShell -Command `"Start-Process $env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe`" -verb runAs -ArgumentList '-NoExit','cd','%V'" -Force | Out-Null
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe,1" -Force | Out-Null
}
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdHere")){
    New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdHere" -Value "Open Command Prompt Here" -ItemType string -Force | Out-Null
    New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdHere\command" -Value 'cmd.exe /k pushd %L' -ItemType string -Force | Out-Null
    New-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdHere" -Name "Icon" -Value "C:\Windows\System32\cmd.exe,0" -Type string -Force | Out-Null
}

# Explorer background
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere")){
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere" -Value "Open PowerShell Here" -Force | Out-Null
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere\command" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location '%V'" -Force | Out-Null
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe" -Force | Out-Null
}
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin")){
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin" -Value "Open PowerShell Here (Admin)" -Force | Out-Null
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin\command" -Value "PowerShell -Command `"Start-Process $env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe`" -verb runAs -ArgumentList '-NoExit','cd','%V'" -Force | Out-Null
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe,1" -Force | Out-Null
}
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdHere")){
    New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdHere" -Value "Open Command Prompt Here" -ItemType string -Force | Out-Null
    New-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdHere\command" -Value 'cmd.exe /k pushd %L' -ItemType string -Force | Out-Null
    New-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdHere" -Name "Icon" -Value "C:\Windows\System32\cmd.exe,0" -Type string -Force | Out-Null
}

# root drives
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere")){
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere" -Value "Open PowerShell Here" -Force | Out-Null
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere\command" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location '%V'" -Force | Out-Null
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe" -Force | Out-Null
}
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin")){
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin" -Value "Open PowerShell Here (Admin)" -Force | Out-Null
    New-Item -ItemType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin\command" -Value "PowerShell -Command `"Start-Process $env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe`" -verb runAs -ArgumentList '-NoExit','cd','%V'" -Force | Out-Null
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin" -Name "Icon" -Value "$env:SystemRoot\system32\WindowsPowerShell\v1.0\powershell.exe,1" -Force | Out-Null
}
if(-not (Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdHere")){
    New-Item -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdHere" -Value "Open Command Prompt Here" -ItemType string -Force | Out-Null
    New-Item -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdHere\command" -Value 'cmd.exe /k pushd %L' -ItemType string -Force | Out-Null
    New-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdHere" -Name "Icon" -Value "C:\Windows\System32\cmd.exe,0" -Type string -Force | Out-Null
}

Since the file that’s dot sourced in my PowerShell profile is shared among all of my machines, I tossed the above code in it to ensure that all machines are configured this way, and to ensure that if I reload one of them, it gets reconfigured with no additional work.

Moving to sub-menu

If your right-click menu is already full of other options, and you’d rather have them on a menu when you hit SHIFT+Right Click, that’s easy. We add a property called “extended”, with an empty value, to the key for each option. We can do them all at once using this code (after running the above code first):

#folders
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere" -Name "extended" -Value $null | Out-Null
}
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin" -Name "extended" -Value $null | Out-Null
}
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdPromptHere" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdPromptHere" -Name "extended" -Value $null | Out-Null
}

#background
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere" -Name "extended" -Value $null | Out-Null
}
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin" -Name "extended" -Value $null | Out-Null
}
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdPromptHere" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdPromptHere" -Name "extended" -Value $null | Out-Null
}

#root drives
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere" -Name "extended" -Value $null | Out-Null
}
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin" -Name "extended" -Value $null | Out-Null
}
if(-Not (Get-ItemProperty -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdPromptHere" -Name "extended")){
    New-ItemProperty -PropertyType String -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdPromptHere" -Name "extended" -Value $null | Out-Null
}

Removing the options

If you decide you’re not a fan of the options, removing them is really easy using the Remove-Item cmdlet.

#folders
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHere" -Recurse -Confirm:$False
}
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\PowerShellHereAdmin" -Recurse -Confirm:$False
}
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdPromptHere"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\shell\CmdPromptHere" -Recurse -Confirm:$False
}

#background
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHere" -Recurse -Confirm:$False
}
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\PowerShellHereAdmin" -Recurse -Confirm:$False
}
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdPromptHere"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CmdPromptHere" -Recurse -Confirm:$False
}

#root drives
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHere" -Recurse -Confirm:$False
}
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\PowerShellHereAdmin" -Recurse -Confirm:$False
}
if(Test-Path -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdPromptHere"){
    Remove-Item -Path "Registry::HKEY_CLASSES_ROOT\Drive\shell\CmdPromptHere" -Recurse -Confirm:$False
}

Donations

I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.

 

Categories: PowerShell Tags:

PowerShell Default Parameter Values – Time to Tweak Your Profile Again!

January 18th, 2017 No comments

One of the great things about a PowerShell profile is you get to customize the environment. One nice feature to accomplish this is the ability to set default parameter values for cmdlets. A great write-up about this is available at About Parameters Default Values. You can see similar info by running get-help about_Parameters_Default_Values. I’m not going to go into great detail about the feature, as those two resources are more than sufficient. What I am going to list here are some great examples that I’ve come to love. They are all in my profile.

This first example if my favorite. When you use the Get-Help cmdlet, the resulting info is shown in a popup window. This is great as I can keep it open while I work on my code in the main console window.

$PsDefaultParameterValues.add("Get-Help:ShowWindow",$True)

If you’re an OCD type like me, you want output of commands to be formatted and everything to line up. This example defaults the Format-Table cmdlet to autosize its output.

$PsDefaultParameterValues.add("Format-Table:AutoSize",$True)

We can actually set this behavior for both Format-Wide and Format-Table at the same time (courtesy of TechNet):

$PSDefaultParameterValues.add("Format-[wt]*:Autosize",$True)

How about we capture the output of the last PowerShell command into a variable, such as $0 (courtesy of TechNet):

$PSDefaultParameterValues.add("Out-Default:OutVariable",“0”)

If you can’t tell by my other articles and scripts here, I spend a lot of time writing scripts to help in deployments, migrations, etc. When you’re getting or setting data in Active Directory, the last thing you want is for replication (intersite or intrasite) to be an issue. So, when possible, you specify a specific domain controller to send all of your commands together. This example comes from serverfault:

$PSDefaultParameterValues = @{"*-AD*:Server"='YOUR-CHOSEN-DC'}

Now that’s not completely perfect, as it would have a hard coded DC name. And just our luck, it will be migrated out of existence and then our stuff breaks. So, let’s set a default parameter value with the result of a PowerShell query. In this case, a DC in the same site for each of our Lync/Skype for Business cmdlets that support the Server parameter:

$PSDefaultParameterValues.add("*-Cs*:Server",(Get-ADDomainController -Discover -NextClosestSite))

Or maybe you want to specify the PDC emulator instead? (courtesy Tommy Maynard)

$PSDefaultParameterValues.Add("Get-ADPrincipalGroupMembership:Server",$((Get-ADDomain).PDCEmulator))

Or, we can use a variable. Let’s say we assign $DC to a DC in the same site, and then use that going forward:

$dc = Get-ADDomainController -Discover -NextClosestSite
$PSDefaultParameterValues.add("*-AD*:Server","$dc")
$PSDefaultParameterValues.add("*-Cs*:Server","$dc")

Now, how many times do you get the “Are you sure” prompt? Force the command instead of getting prompted!

$PSDefaultParameterValues['*:Force']= $true

Fellow MVP Boe Prox also has a great list of examples on his post Using $PSDefaultParameterValues in PowerShell. Check it out!

Now, you can list each of these, or any combination, in your profile, each on a separate line. Or, we can use an array to set all at one time:

$PSDefaultParameterValues=@{
'Format-Table:AutoSize'=$True;
'Get-Help:ShowWindow'=$True;
'Send-MailMessage:SmtpServer'=$smtpserver
}

Something to keep an eye on here. In previous examples, I was using the $PSDefaultParameterValues.add method which ADDS the value to the existing list. If you omit the “.add” and instead use “=” or “=@{}”, you are replacing all existing values with what you specify. Additionally, you can use the $PSDefaultParameterValues.remove method to remove specific parameter values and keep any remaining values. An example of removing a single default parameter value:

$PsDefaultParameterValues.remove("Get-Help:ShowWindow",$True)

Are all of these changes permanent? No. They are valid for the life of the PowerShell session. If you need to remove them mid-session, you can clear them using:

$PsDefaultParameterValues.clear()

Defining default parameter values can also be defined at various scopes, as well, including Global, Script, etc. See Get-Help about_Scope for more info.

I mentioned at the beginning that these are great in your profile. Well, they’re great in your scripts as well. They allow for global changes instead of going through a script and updating each call to a cmdlet. By all means, send me your favorites. We’ll build a big list!

Categories: PowerShell Tags: ,

All Skype for Business 2015 Cmdlets and the Default RBAC Roles That Can Use Them

December 23rd, 2016 No comments

Description

In All Lync 2013 Cmdlets and the Default RBAC Roles That Can Use Them and the corresponding 2010 version, I show a table that lists every cmdlet available in a fully patched Lync server environment, and the default permissions for each of the default RBAC roles. Doing one for Skype for Business was always on my list, but I never really got around to it until a visitor recently noted that some of the RBAC permissions changed for existing cmdlets when compared to the Lync Server 2013 list. So I figured it was time to do a new one. The previous versions were all manually created – every row. That was extremely laborious, taking many hours. This time around I automated the info gathering using (what else), PowerShell. This gave me all of the data in a .csv file, and three minutes of styling in Excel, and presto!

One thing I did notice is that there is a small group of cmdlets that don’t yet have a description, synopsis, uri, etc. So you’ll see those blank cells highlighted in bright red for now. I’ve reached out to the Product Group for info on when that info will be available. As soon as I have an answer, I’ll get it posted here.

So the spreadsheet is available below, but what good would a blog article be without some PowerShell code? So here’s the code I came up with to create the spreadsheet.

$objectCollection = @()
foreach ($cmdlet in (Get-Command -Module SkypeForBusiness | Sort-Object Name)){
    Write-Output $cmdlet
    $cmdletHelp = $(Get-Help $cmdlet)
    [string] $Synopsis = $cmdletHelp.Synopsis
    [string] $URI = (($cmdletHelp.relatedLinks.navigationLink | Where-Object {$_.linkText -match "Online Version"}).uri) -replace "EN-US/",""
    [string] $RBAC = "Get-CsAdminRole | Where-Object {`$`_.Cmdlets `–imatch `"$cmdlet`"}"
    $rbacroles = Get-CsAdminRole | Where-Object {$_.Cmdlets –imatch "$cmdlet"}

    [bool] $RbacCSAdministrator = $rbacroles.identity -icontains "CSAdministrator"
    [bool] $RbacCSVoiceAdministrator = $rbacroles.identity -icontains "CSVoiceAdministrator"
    [bool] $RbacCSUserAdministrator = $rbacroles.identity -icontains "CSUserAdministrator"
    [bool] $RbacCSResponseGroupAdministrator = $rbacroles.identity -icontains "CSResponseGroupAdministrator"
    [bool] $RbacCSLocationAdministrator = $rbacroles.identity -icontains "CSLocationAdministrator"
    [bool] $RbacCSArchivingAdministrator = $rbacroles.identity -icontains "CSArchivingAdministrator"
    [bool] $RbacCSViewOnlyAdministrator = $rbacroles.identity -icontains "CSViewOnlyAdministrator"
    [bool] $RbacCSServerAdministrator = $rbacroles.identity -icontains "CSServerAdministrator"
    [bool] $RbacCSHelpDesk = $rbacroles.identity -icontains "CSHelpDesk"
    [bool] $RbacCSResponseGroupManager = $rbacroles.identity -icontains "CSResponseGroupManager"
    [bool] $RbacCSPersistentChatAdministrator = $rbacroles.identity -icontains "CSPersistentChatAdministrator"

    $object = New-Object –Type PSObject
    $object | Add-Member –Type NoteProperty –Name Cmdlet -Value $cmdlet
    $object | Add-Member –Type NoteProperty –Name Description -Value $Synopsis
    $object | Add-Member –Type NoteProperty –Name Uri -Value $URI
    $object | Add-Member –Type NoteProperty –Name Validate -Value $rbac
    $object | Add-Member –Type NoteProperty –Name CSAdministrator -Value $RbacCSAdministrator
    $object | Add-Member –Type NoteProperty –Name CSArchivingAdministrator -Value $RbacCSArchivingAdministrator
    $object | Add-Member –Type NoteProperty –Name CSHelpDesk -Value $RbacCSHelpDesk
    $object | Add-Member –Type NoteProperty –Name CSLocationAdministrator -Value $RbacCSLocationAdministrator
    $object | Add-Member –Type NoteProperty –Name CSPersistentChatAdministrator -Value $RbacCSPersistentChatAdministrator
    $object | Add-Member –Type NoteProperty –Name CSResponseGroupAdministrator -Value $RbacCSResponseGroupAdministrator
    $object | Add-Member –Type NoteProperty –Name CSResponseGroupManager -Value $RbacCSResponseGroupManager
    $object | Add-Member –Type NoteProperty –Name CSServerAdministrator -Value $RbacCSServerAdministrator
    $object | Add-Member –Type NoteProperty –Name CSUserAdministrator -Value $RbacCSUserAdministrator
    $object | Add-Member –Type NoteProperty –Name CSViewOnlyAdministrator -Value $RbacCSViewOnlyAdministrator
    $object | Add-Member –Type NoteProperty –Name CSVoiceAdministrator -Value $RbacCSVoiceAdministrator
    $objectCollection += $object
}
$objectCollection | Export-Csv -Path $env:UserProfile\desktop\SfB2015cmdlets.csv -NoTypeInformation -Encoding UTF8

Donations

I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.

Download

v1.0 – 12-23-2016 – SkypeForBusiness2015cmdlets.xlsx

Function: Remove-IisLogFile – Purging Old IIS Log Files with PowerShell

November 21st, 2016 No comments

PowerShell-logo-128x84If you’re not careful, your server running IIS can create a LOT of logs. The default location for logs is in a sub-folder for the specific web site in c:\inetpub\logs\logfiles\. You can imagine the problems that will happen when your OS drive fills up with logs… things tend to not go so well, and the phone starts to ring. We can’t really just disable logging, as log files are an invaluable resource used in troubleshooting, planning, and maintenance.

Ryan over at Ryadel wrote a great article on adjusting the logging for IIS to be a little more helpful, and to minimize bloat. But we still need to watch for the accumulation of logs and the disk space they take. Ryan includes a two-line method of cleaning up the files in a single IIS site. But some servers, such as Lync and Skype for Business front end servers, have multiple web sites defined. I’ve taken Ryan’s method a bit further by incorporating an idea presented in a Stack Overflow thread, tweaked it a bit, and now we have some code that will clean up all log files that are older than 180 days for all websites on a server. Obviously, that time frame can be adjusted. Here it is the simple method:

Import-Module WebAdministration
$start = (Get-Date).AddDays(-180) 
foreach($WebSite in $(Get-WebSite)) {
  $logFile = "$($Website.logFile.directory)\w3svc$($website.id)".replace("%SystemDrive%",$env:SystemDrive)
  if (Test-Path $logfile){
    Get-ChildItem -Path "$logFile\*.log" | Where-Object {$PSItem.LastWriteTime -lt $start} | Remove-Item -Force
    # Write-Output "$($WebSite.name) [$logfile]"
  }
}

By adjusting the number at the end of the second line, we tailor the maximum age of the logs. In the above example, we’re keeping 180 days of them. We could put that code into a script and call it with a scheduled task to automate the cleanup, essentially creating a self-cleaning server. We can also wrap that code into a function and toss it into the PowerShell profile on the web server, allowing us to run it whenever we need to:

function Remove-IisLogFile{
    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [int] $age = 180
    )
    Import-Module WebAdministration
    $start = (Get-Date).AddDays(-$age) 
    foreach($WebSite in $(Get-WebSite)) {
      $logFile = "$($Website.logFile.directory)\w3svc$($website.id)".replace("%SystemDrive%",$env:SystemDrive)
      if (Test-Path $logfile){
        Get-ChildItem -Path "$logFile\*.log" | Where-Object {$PSItem.LastWriteTime -lt $start} | Remove-Item
        # Get-ChildItem -Path "$logFile\*.log" | Where-Object {$PSItem.LastWriteTime -lt $start}
        Write-host "$($WebSite.name) [$logfile]"
      }
    }
}

Then we can call it, optionally specifying the age of the log files we want to purge using the -age parameter. I incorporate the Test-Path code to ensure we’re not throwing an error for a website that is stopped and has never run. This is often the case in the aforementioned Lync/Skype for Business servers, where the default web site is disabled.

As you can see, PowerShell can be great at making sure your servers don’t get packed full of log files, while still maintaining enough logs to be helpful.

Donations

I’ve never been one to really solicit donations for my work. My offerings are created because *I* need to solve a problem, and once I do, it makes sense to offer the results of my work to the public. I mean, let’s face it: I can’t be the only one with that particular issue, right? Quite often, to my surprise, I’m asked why I don’t have a “donate” button so people can donate a few bucks. I’ve never really put much thought into it. But those inquiries are coming more often now, so I’m yielding to them. If you’d like to donate, you can send a few bucks via PayPal at https://www.paypal.me/PatRichard. Money collected from that will go to the costs of my website (hosting and domain names), as well as to my home lab.