Archive

Posts Tagged ‘PowerShell’

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.

Script: Set-CsFeatures.ps1 – Easily Install Prerequisites and Tools for Lync Server 2013 and Skype for Business Server 2015

October 24th, 2016 19 comments

skype_for_business_secondary_blue_rgbDescription

Installing Skype for Business and Lync servers is usually boring if you’re a consultant who does it often. Making sure the server specs are right, installing OS features, configuring NICs, etc. It’s even more boring if you’re building a bunch of servers at one time. There’s always a chance for human error, too. So why not automate as much as possible? That’s what I was after when I built the Lync Server 2010 prereq script, then the Lync Server 2013 prereq script. And it’s certainly what I’m after for Skype for Business Server 2015. This time, however, I opted to not have a separate script for Skype for Business. Many of the requirements are the same, or just slightly different, than Lync Server 2013. So I just added the SfB functionality to the 2013 script, and updated everything as a whole.

When calling the script, one only needs to specify the –Skype4b switch to put the script into “Skype for Business mode”. Not specifying that switch cause a pop-up to appear, asking what mode you’d like. The menus don’t change based on what mode the script is in. Options for only one platform are clearly noted. Otherwise, the options automatically adjust for the platform you’ve chosen. The menu starts out with core prerequisite options for common Lync/SfB roles, followed by Microsoft tools and resources, some third-party tools and options, and then some sub-menus. Sub-menus are broken down by Misc server config, Desktop shortcuts, Taskbar shortcuts, Downloads, and Security options. As you can see, there are TONS of options. I’m not going to list every menu and option here, as the nature of the script means I’ll be adding/updating things as people request them, or as vendors update/alter their offerings. Just note that the options from the 2013 script have been moved around a little bit as I try to keep things organized.

This version also uses my new method of checking for updates, as mentioned in Function: Get-UpdateInfo – Making It Easy for Your Users to Get the Latest Version of Your Scripts. When a new version is available, you’ll get a pop-up notifying you.

If you’re aware of a third-party product, or even Microsoft product, that is a good match for Skype for Business servers, let me know. I’m happy to take a look and see if it would make a good addition to the script.

Super big thanks to my beta testers for supplying bug reports, suggestions, and comments.

Syntax

C:\Set-CsFeatures.ps1 [-TargetFolder <String>] [-WindowsSource <String>] [-SQLPath <String>] [-InitialMenuOption <Int32>] [-IncludeSSMS] [-IncludeTelnet] [-IncludeFW] [-IncludeHighPower] [-IncludeStandard] [-GetInfoFromRegistry] [-OWASOveride] [-DownloadOnly] [-SkipCoreCheck] [-Tail] [-Skype4b] [-SkipUpdateCheck] [-WhatIf] [-Confirm] [-IncludeTotalCount] [-Skip <UInt64>] [-First <UInt64>] [<CommonParameters>]

C:\Set-CsFeatures.ps1 [-TargetFolder <String>] [-GetInfoFromRegistry] [-OWASOveride] [-DownloadAll] [-SkipCoreCheck] [-Tail] [-Skype4b] [-WhatIf] [-Confirm] [-IncludeTotalCount] [-Skip <UInt64>] [-First <UInt64>] [<CommonParameters>]

C:\Set-CsFeatures.ps1 [-GetInfoFromRegistry] [-ClearRunningStatus] [-WhatIf] [-Confirm] [-IncludeTotalCount] [-Skip <UInt64>] [-First <UInt64>] [<CommonParameters>]

C:\Set-CsFeatures.ps1 [-GetInfoFromRegistry] [-Skype4b] [-WhatIf] [-Confirm] [-IncludeTotalCount] [-Skip <UInt64>] [-First <UInt64>] [<CommonParameters>]

Examples

.\Set-CsFeatures.ps1 -Skype4b

Runs script in Skype for Business mode. Options chosen while running in this mode are tailored to Skype for Business. Not specifying this option will cause a pop-up prompt when the script starts, allowing a user to choose the desired mode.

.\Set-CsFeatures.ps1

Runs script with default values.

.\Set-CsFeatures.ps1 -WindowsSource "d:"

Runs script with the location defined for the Windows Server 2012/2012 R2 installation files.

.\Set-CsFeatures.ps1 -SQLPath "d:\sqlexpress"

Runs the script and installs any required SQL Express instances in the specified location.

.\Set-CsFeatures.ps1 -TargetFolder "d:\installbits"

Runs the script, and saves any downloaded files and written logs in the specified location instead of the default “c:\_install”.

.\Set-CsFeatures.ps1 -InitialMenuOption 3

Runs the script, and automatically starts option 3 (Front End server). Once it’s finished with that option, the script functions as normal, and displays the menu. NOTE: only options from the main menu can be specified. Options in sub-menus are not available with -InitialMenuOption.

.\Set-CsFeatures.ps1 -tail

Runs script with default values, but also shows an additional PowerShell window showing a live running log file.

Parameters

-TargetFolder

Defines the location for any downloaded files. Defaults to “c:\_install”. Additionally, log files generated by this script are located in a sub-folder of TargetFolder called “logs”. TargetFolder does not support paths with spaces, but does support non-hidden UNC paths.

-WindowsSource

Defines the location of the Windows Server installation files. This is needed to install .Net 3.5 since those files are not installed on the server by default. Defaults to first detected CD-ROM/DVD drive. This can be a local file path, path to an .ISO file, or a non-hidden UNC path.

-SQLPath

Defines the desired installation path for SQL Express. Defaults to “c:\Program Files\Microsoft SQL Server”.

-InitialMenuOption

Allows you to start the script with the option you want, without first displaying the menu.

-IncludeSSMS

If specified, will include SQL Server Management Studio automatically when prereqs are installed for any server that has SQL Express instances. If not specified, a prompt will appear.

-IncludeTelnet

If specified, will include Telnet automatically when prereqs for Front End servers, Director servers, Mediation servers, Edge servers, and/or Persistent Chat servers are installed. If not specified, a prompt will appear.

-IncludeFW

If specified, will include the firewall rules for Get-CsConnections automatically when prereqs for Front End servers are installed. If not specified, a prompt will appear.

-IncludeHighPower

If specified, tells the script to automatically set the Power Config on the server to High Power. This is instead of the script prompting. This option is available for all server roles.

-IncludeStandard

If specified, tells the script to include the extra SQL Express instance required for Standard Edition front end servers. This is instead of the script prompting.

-GetInfoFromRegistry

This value is only used during mid-prereq reboots. It is automatically set and read by the script, and should never be manually specified.

-OWASOveride

Don’t use this parameter. It’s for internal testing only. Using it can render the server unusable.

-DownloadOnly

Tells this script to not install or configure anything – just download the files. This is useful if you’re going to be building servers that do not have Internet access and want to fetch the files beforehand. The big difference between this option and -DownloadAll, is that this option presents the normal menus, and allows you to download files for the options you pick. The -DownloadAll option downloads ALL files needed for ALL options.

-DownloadAll

Tells this script to not install or configure anything – just download ALL of the files. This is useful if you’re going to be building servers that do not have Internet access and want to fetch the files beforehand from a desktop computer. The big difference between this option and -DownloadOnly, is that this option downloads ALL files needed for ALL options, whereas -DownloadOnly allows a user to download files for specific options they choose.

-ClearRunningStatus

This switch forces the running status to be reset. This option should ONLY be used if the script exits/aborts dirty, and attempts to run the script again yield a “Script is already running” message.

-SkipCoreCheck

When specified, skips the check for Server Core. It is not meant to be called manually, as it’s used when the script needs to restart after a server reboot.

-Tail

When specified, opens another PowerShell session and tails the log file, similar to *nix. This is really only beneficial during troubleshooting.

-Skype4b

When specified, uses values specific to Skype For Business Server 2015 for prerequisites. If this option is NOT specified a pop-up will appear, asking which mode the script should operate in: Lync Server 2013 or Skype for Business Server 2015.

-SkipUpdateCheck

When specified, skips the check for a newer version of the script. This option is included mainly for when the script reboots the server.

Installation

No installation is necessary.

Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.

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.

Frequently Asked Questions

Question: Why doesn’t this script support Windows Server 2008 R2?

Answer: I get asked this all the time. There are several reasons. The first is that out of the box, Server 2008 R2 has PowerShell 2.0 installed, and this script is written in PowerShell 3.0. Requiring you to upgrade to PowerShell 3.0 first, before running a script that installs prerequisites, seems counter-intuitive. And converting the script to just use PowerShell 2.0 is taking a step backwards, especially considering that the current version of PowerShell is 5.0, and even as this is written, 5.1 is in preview.

Next is sheer time. I test changes I make. And then I test them again. And then I choose different options and combinations and test them. Testing on just Server 2012 and Server 2012 R2 is exhausting. Adding Server 2008 R2 would mean even more testing, plus I’d have to add those resources in my already overtaxed test labs. That would slow down my ability to add new features and test fixes.

Third is that Server 2008 R2 is three versions back. Get with the times already!


Question: Does the script support Windows Server 2016?

Answer: No, and the primary reason is that Lync Server 2013 and Skype for Business Server 2015 are not supported on Windows Server 2016. Once they are supported on Windows Server 2016 (and it will likely only be Skype for Business Server 2015 that’s supported), I’ll adjust the script as needed. I’ve already done some preliminary work.


Question: Can you add feature x?

Answer: I LOVE getting feature requests. Seriously! Best method to suggest features is to send me an email. My email address is in the comment section at the top of every script I publish. Please be detailed in what you’d like to see, as well as any scenarios you’d use the option (so I can try to duplicate testing). This also goes for additional tools, whether Microsoft or third-party.


Question: How do I submit bug reports?

Answer: Email is best. Grab my email address from the comment section at the top of the script. Please be VERY detailed. Please include screen shots if possible, and ALWAYS include the log file. If the script will start, select option 96, “Report a bug/problem with this script”. If you’re not using the latest version of the script, please download it from the Downloads section below and see if you can duplicate the problem before reporting it.


Question: What if my server doesn’t have Internet access?

Answer: Fear not. Download the required files using either the -DownloadOnly or -DownloadAll options from another machine and place them in the TargetFolder, which is c:\_install by default. The script looks to see if the file is available locally before attempting to download. An exception to this is the latest cumulative update, which is always downloaded, since the URL and file name don’t change, even when the version does.


Question: When I run the script again, I get “Script already running”

Answer: This is because the script didn’t exit gracefully. Many reasons this can happen, such as rebooting the server while it’s still running. If you’re positive it’s not running anywhere else (including by other users logged into the same server), run the script with the -ClearRunningStatus switch to clear that flag. Then run it as normal.


Question: Is there an option to specify where (i.e. path) all of the various tools are installed?

Answer: No. And not for a lack of trying. Some tools don’t support automated installs with a specified path. And some of those that DO, actually still dump some core files in a “default” location. The more I tried to come up with the solution, the more I realized that it would entail a substantial amount of overhead in the script.


Question: Why does the script report an unsupported version of .NET Framework?

Answer: Because Lync Server 2013 and Skype for Business Server 2015 don’t support the version detected. Once they do, I’ll adjust the script accordingly.


Download

v4.10 – 05-15-2017 – Set-CsFeatures.v4.10.zip

v4.09 – 05-13-2017 – Set-CsFeatures.v4.09.zip

v4.08 – 04-19-2017 – Set-CsFeatures.v4.08.zip

v4.07 – 04-14-2017 – Set-CsFeatures.v4.07.zip

v4.06 – 02-05-2017 – Set-CsFeatures.v4.06.zip

v4.05 – 11-04-2016 – Set-CsFeatures.v4.05.zip

v4.04 – 11-02-2016 – Set-CsFeatures.v4.04.zip

v4.03 – 11-01-2016 – Set-CsFeatures.v4.03.zip

v4.02 – 10-28-2016 – Set-CsFeatures.v4.02.zip

v4.01 – 10-25-2016 – Set-CsFeatures.v4.01.zip

v4.0 – 10-24-2016 – Set-CsFeatures.v4.0.zip

Changelog

See the changelog for information on what’s changed/included in each version.

Function: Get-UpdateInfo – Making It Easy for Your Users to Get the Latest Version of Your Scripts

October 10th, 2016 No comments

updatepromptDescription

As a PowerShell developer, you always want your users to have the latest version of a script. It makes support a lot easier, while also making sure that users have the latest features and bug fixes. But how to encourage that? Well, for me, users of my scripts are typically not within the same environment as me. So Group Policy Objects, logon scripts, etc, aren’t a solution. Having the script automatically check for an update is much easier, and doesn’t require anything from the user1. So let’s take a look at a quick and easy method.

First, we need a repository where the update information will be held. XML is perfect for this. In this example, I created the following file, and saved it as version.xml:

<?xml version="1.0"?>
<catalog>
<article id="1697">
<title>Set-CsFeatures.ps1</title>
<author>Pat Richard</author>
<version>3.9.57</version>
<publish_date>2016-10-08</publish_date>
<description>Installs all required Windows 2012/Windows 2012 R2 components & optional tools.</description>
</article>
</catalog>

This file can reside anywhere. A file path, a web site, wherever. I chose a website for the reasons I mentioned above. You can see the above file in action at http://www.ucunleashed.com/downloads/version.xml. Some key points to the file. Each article I publish going forward will have it’s own “article” node. The ID I chose to tie to it is also the ID of the article’s URL, for consistency sake. In this example, 1697 is the prereq script seen at http://www.ucunleashed.com/1697. The version value is the version of the latest general availability (“GA”) build. We’ll query that value, compare it against the version of the script running the query, and see if it’s newer. Note that there is some other info in the XML file, and that’s irrelevant to what we’re discussing here.

[xml] $xml = (New-Object System.Net.WebClient).DownloadString("http://www.ucunleashed.com/downloads/version.xml")
$Ga = ($xml.catalog.article | Where-Object {$_.id -eq $article}).version

We supply the $article value when making the call. After that, it’s a simple comparison. In the prereq script, near the beginning, I assign a variable, $version, with a value. Let’s say it’s “3.9.55”. We compare $Ga against $Version

$Ga -gt $Version

If it’s true, we know a newer version exists. If it’s false, we know the currently running script is the latest version. In theory, we could also use this to alert of a regression in case we needed to downgrade (gasp!). So let’s put this together. We assign a variable, $xml, to the results of downloading an xml file. Then, we assign $ga to the value of “version” for the specific node within the xml file that contains the info for the article. Lastly, we do our comparison and give some output if there is an update.

[xml] $xml = (New-Object System.Net.WebClient).DownloadString("http://www.ucunleashed.com/downloads/version.xml")
$Ga = ($xml.catalog.article | Where-Object {$_.id -eq $article}).version
if ($Ga -gt $Version){Write-Output "A new version is available!"}

Now, obviously, we can pretty this up a bit. But before we do that, let’s think of issues we could run into. The big one is making sure we have an Internet connection to use to check the XML file. As much as we can often assume there will be one, a LOT of organizations block Internet access to servers as part of their security posture. So we shouldn’t assume. We can check using the following:

[bool] $HasInternetAccess = ([Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]'{DCB00C01-570F-4A9B-8D69-199FDBA5723B}')).IsConnectedToInternet)

And then using an IF loop against $HasInternetAccess. So let’s throw this all into a function we can incorporate into our scripts and modules:

function Get-UpdateInfo {
  [CmdletBinding(SupportsShouldProcess, SupportsPaging)]
  param (
    # Article/script to check for updates
    [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [string] $article
  )
  [bool] $HasInternetAccess = ([Activator]::CreateInstance([Type]::GetTypeFromCLSID([Guid]'{DCB00C01-570F-4A9B-8D69-199FDBA5723B}')).IsConnectedToInternet)
  if ($HasInternetAccess){
    [xml] $xml = (New-Object System.Net.WebClient).DownloadString("http://www.ucunleashed.com/downloads/version.xml")
    $Ga = ($xml.catalog.article | Where-Object {$_.id -eq $article}).Version    
    if ($Ga -gt $version){
      Write-Log -Level Warn -Message "Outdated version. Version $Ga is latest version. Prompting user" -NoConsole
      $wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop
      $updatePrompt = $wshell.Popup("A new version ($ga) of the script is available. Would you like to download it?",0,"A new version is available",68)
      if ($updatePrompt -eq 6){
        Start-Process "http://www.ucunleashed.com/$article"
      }
    }
  }else{
    Write-Output "No Internet connectivity. Unable to check online for update info."
  }
} # end function function Get-UpdateInfo

Here we incorporate a simple ComObject popup message to ask if the user wants to download the new version. Since we have assigned the GA number to $ga, we can use that in the popup text, as well, as shown in the image at the beginning of this article. If $updatePrompt is “6”, then the user clicked “Yes” on the popup, and we can take action such as opening a browser window and navigating to the articles page. Or we could download a file, or any of a number of actions. If $updatePrompt is “7”, then the user clicked “No”.

So, as you can see, it’s really not that hard to add an update checker to your scripts. When you release a new version, simply update the XML file to reflect accordingly.

Note: Take care in what kind of characters are in the XML file. Some special characters, such an ampersand (“&”), aren’t handled very well. When in doubt, open a browser window and navigate to the file.

1 – Depending on the action you require once it’s known an update is available.

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.

One Liner: Enabling Mapped Drives in Elevated PowerShell Sessions

July 18th, 2016 No comments

If you’ve worked with mapped drives in PowerShell sessions, you know it’s problematic to access mapped drives from an elevated PowerShell session when UAC is configured to prompt to prompt for credentials. Microsoft released a TechNet KB article on this issue quite some time ago. The article shows different ways to address the problem, from using the Local Security Policy, mapping the drives again in the elevated prompt, and using the registry. We’ll focus on the registry here for several reasons. The first is that using the local security policy seems burdensome; mapping the drives again seems redundant, and potentially confusing if the original mappings change and the ones in your PowerShell session don’t; and thirdly, and most important, we’re talking PowerShell here!

The local security policy really just changes registry settings under HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System, so using PowerShell to set registry settings accomplishes the same thing. We can add new property, EnableLinkedConnections using the New-ItemProperty cmdlet, which also lets us set its value to 1. A value of 1 will enable the mapped drives in elevated session, while a value of 0, or completely removing the property, disables those mapped drives in an elevated session. So let’s implement this:

New-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name EnableLinkedConnections -Value 1 -PropertyType "DWord"

Now, if you want to put this in your PowerShell profile, then it will get processed every time. The problem is that you’ll get a “The property already exists” exception error every time it runs after the first time. So, we just wrap it in an IF statement using Get-ItemProperty, checking to see if it exists first. If not, create the item property.

if (-not (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name EnableLinkedConnections)){
  New-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name EnableLinkedConnections -Value 1 -PropertyType "DWord"
}

Why would we want to include this in our profile? Because when we get new machines, or reload an existing machine, we don’t want to have to go back and manually configure everything again. We can just manually run the profile script and have it configure everything for us.

Categories: PowerShell Tags: , ,

One liner: Find Lync/Skype for Business Users Whose Extension Doesn’t Match Part of Their DID

September 18th, 2015 2 comments

Description

 

Get-CsUser -Filter {LineURI -ne $null} | Where-Object {$_.LineURI.Split("=")[1] -NotMatch $_.LineURI.Substring($_.LineURI.Split(";")[0].Length -4,4)} | Select-Object DisplayName,LineURI | Sort-Object DisplayName

One Liner – See Number Of Connected Users, Endpoints On A Lync Front End Server

January 22nd, 2015 4 comments

A question went around an internal DL at work today asking if anyone knew off the top of their head the name of performance counters that show connected users and endpoints. While digging up the answer, I started thinking – this would be a great little one liner.

My esteemed colleague Ron Cook (@roncook925) beat me to supplying the answer to the DL question. The two counters are:

LS:USrv – Endpoint Cache\USrv – Active Registered Endpoints
LS:USrv – Endpoint Cache\USrv – Active Registered Users

Endpoints is always higher than users, in my experience. There are always some users who are connected via mobile devices and rich client, or via OWA, or LPE. So I like to query both.

PowerShell has a great cmdlet called Get-Counter which, as you can guess, can query performance counters. There’s a pretty good tutorial on how to retrieve perfmon counter data for Lync related counters by the Lync PowerShell group at Microsoft in How Do We Love Performance Counters” Let Us Count the Ways. So let’s take a look at how we can get the data we need.

In this case, we’ll query the two counters mentioned above with one line. This is supported in Get-Counter by just separating the counters with a comma. We’ll select an expanded property called CounterSamples, which holds the data we need (among other info). And lastly, we’ll output the path (counter name), and something called the CookedValue, which is the actual counter value contained within CounterSamples. I know, CookedValue sounds like it could be just made up numbers, like those you get from a shifty accountant. But it is truly the value we want.

Plug this into your console as one long line:

Get-Counter "\LS:USrv - Endpoint Cache\USrv - Active Registered Endpoints","\LS:USrv - Endpoint Cache\USrv - Active Registered Users" | Select-Object -ExpandProperty CounterSamples | Format-Table Path,CookedValue -Auto

That will give you a quick point-in-time snapshot of the number of users and endpoints connected to the front end, as shown below.

perfmon

The blurred text is just the front end name. If you’d like to query a remote front end, just tack on the ComputerName parameter, such as:

Get-Counter "\LS:USrv - Endpoint Cache\USrv - Active Registered Endpoints","\LS:USrv - Endpoint Cache\USrv - Active Registered Users" -ComputerName frontend.contoso.com | Select-Object -ExpandProperty CounterSamples | Format-Table Path,CookedValue -Auto

For those wondering why I’m using Format-Table and the -Auto parameter, it’s because the counter path value is so long that it would otherwise get truncated short enough to where you wouldn’t know which counter was tied to which value.

Changelog: New-SignedScript

June 10th, 2014 No comments

This is the changelog page for New-SignedScript. You will find a complete list of released versions, their dates, and the features and issues addressed in each. Please refer to the script’s main page for more information including download links, installation details, and more.

v1.3 – 09-18-2016

  1. Now includes a selection box when more than one valid code signing certificate is found.

v1.1 – 06-10-2014

  1. Better handling when there is more than one code signing cert. Script now finds the first valid code signing cert and uses that.
  2. Better validation that the script is successfully signed

v1.0 – 09-20-2012

  1. Initial version
Categories: PowerShell Tags: , ,

Script: Get-CsFederatedConversationDetails.ps1 – See Stats About Conversations With Specific Federated Domains

May 13th, 2014 5 comments

Lync 2013 logo 128x128Description

Richard Schwendiman, PFE at Microsoft, came up with a great SQL query that you could plug into SQL Server Management Studio to see time & date info for conversations with federated or PIC domains. In Richard’s case, he used the aol.com PIC domain. Since PIC federation with AOL and Yahoo is ending next month, I thought this was great timing on Richard’s part. But sometimes, Lync admins can’t login to SQL servers to run queries due to security policy. Plus, the query is something you’d have to keep handy and edit accordingly each time you wanted to get data. So I figured – hey, why not whip up a quick script to allow an admin to query SQL for this data, allowing for any domain and time frame to be specified? Poof – out comes my script.

This script will query a specific SQL server for information about a specific federated SIP domain. The domain does NOT need to be in the allowed domains list if you’re open federating. Any SIP domain name works. You can specify a start date/time in the yyyy-MM-dd format, such as 2014-05-13 using the -TimeSpan parameter. Or, you can use some handy ranges I’ve included, including LastWeek (the last 7 days), Last30Days, Last year (last 365 days), FirstOfYear or ThisYear (since Jan. 1), FirstOfMonth or ThisMonth (since the 1st of the month), FirstOfWeek or ThisWeek (since Sunday). Optionally, you can specify an end date/time in the yyyy-MM-dd format. This script will default to FirstOfYear with no end date, and aol.com for the domain. As we see below, only the SQL server holding the LcsCDR database is queried.

aol3

Now, from this output, we see that there is not a lot of communications with people on AOL since the first of the year. The upcoming change should have very little impact.

If you’re using a named instance in SQL, you can specify it as well.

The script outputs a full object, just like other cmdlets, so you can pipe it to other commands to alter the display, including sorting, or my favorite, Out-GridView, as well as outputting to files such as .csv.

Hopefully, this tool will make life a little easier in digging out data.

Syntax

Get-CsFederatedConversationDetails.ps1 [[-SqlServer] ] [[-SqlInstance] ] [[-SipDomain] ] [[-TimeSpan] <object>] [[-EndDate] <object>] [-WhatIf ] [-Confirm ] [<commonparameters>]</commonparameters></object>

Installation

Execution Policy: Third-party PowerShell scripts may require that the PowerShell Execution Policy be set to either AllSigned, RemoteSigned, or Unrestricted. The default is Restricted, which prevents scripts – even code signed scripts – from running. For more information about setting your Execution Policy, see Using the Set-ExecutionPolicy Cmdlet.

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.

Assumptions

None

Download

v1.0 – 05-13-2014 – Get-CsFederatedConversationDetails.v1.0.zip

Changelog

See the changelog for information on what’s changed/included in each version.