Archive

Archive for the ‘PowerShell’ Category

Function: Get-PstFiles – List All .pst Files In A Given Path, With Name, Path, Size, and Owner

May 13th, 2013 2 comments

Exchange 2013 logo 128x128Description

If you need to make a list of the .pst files in a given path, this function should help. It will get all .pst files, their name, path, size (in MB), and owner. This should help if you’re looking at importing the files in Exchange Server. If you’d like to export the results to a file, use the -file switch, with a filename, and the function will save the info to a .csv file.

Syntax

Get-PstFiles [[-path] ] [[-filter] ] [[-file] ] [-WhatIf] [-Confirm] []

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.1 – 05-13-2013 Get-PstFiles.v1.1.zip

Syntax Highlighting File for UltraEdit Includes Exchange 2010/2013, Lync 2010/2013, and ActiveDirectory cmdlets

May 8th, 2013 2 comments

In a previous post, Exchange 2010 and Lync 2010 PowerShell syntax highlighting file for UltraEdit, I included the cmdlets for both Exchange 2010 and Lync 2010. In this new file, I’ve included Lync 2010, Lync 2013, Exchange 2010, Exchange 2013, and Active Directory cmdlets for highlighting. If you use UltraEdit, this wordfile may make life a little easier.

Download the file here: UltraEditPowerShellLyncExchangeAD.zip

One liners: Get All Exchange Users Who Are Configured for Forwarding

May 7th, 2013 4 comments

Exchange 2013 logo 128x128Due to some legal requirements, I had a needed to list all users who were configured in Exchange to forward elsewhere. This was to ensure that mail wasn’t automatically leaving the environment. A simple, single line in the shell is all that’s needed to give me what I need.

Open Exchange Management Shell, and enter this:

Get-Mailbox -Resultsize Unlimited | Where-Object {$_.ForwardingAddress}

We can clean this up and make it a little more presentable using something like:

Get-Mailbox -Resultsize Unlimited | Where-Object {$_.ForwardingAddress} | Select-Object Name, @{Expression={$_.ForwardingAddress};Label="Forwarded to"}, @{Expression={$_.DeliverToMailboxAndForward};Label="Mailbox & Forward"}

And the results are a small table that shows the user name, which object mail is being forwarded to, and whether the mailbox is configured to both store and forward:

forwardedusers

This allowed me to take a look at those user accounts, and disable the forwarding, forcing the users to use their Exchange mailbox.

For a long list, or if you just want the info in a file, we can export the results to a .csv using Export-Csv. To do this, use:

Get-Mailbox -Resultsize Unlimited | Where-Object {$_.ForwardingAddress -ne $null} | Select-Object Name, @{Expression={$_.ForwardingAddress};Label="Forwarded to"}, @{Expression={$_.DeliverToMailboxAndForward};Label="Mailbox & Forward"} | Export-Csv c:\forwardedusers.csv -NoTypeInformation

Function: Set-TimeZone – Configure Time Zone via PowerShell

March 4th, 2013 1 comment

Powershell_logo-137x137Note: This functionality was added into PowerShell version 3.1.For more information, see https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.management/set-timezone

It’s easy to just double-click on the time in the system tray and set the time and time zone when building a new server. You may even have a GPO that does this for you. That’s all fine and dandy. But GPOs don’t work for member servers, and if you build a lot of servers, like I do, it’s a repetitive task – something perfectly suited for PowerShell.

With this function, we do nothing more than call tzutil.exe with the timezone name. Nothing fancy. I added some validation to make sure that the timezone specified is truly one of the 100 valid time zones that tzutile.exe should recognize and accept.

So, now, in your server provisioning script, merely call the function, Set-TimeZone, with the desired timezone. For example:

Set-TimeZone "Pacific Standard Time"

The script defaults to “Eastern Standard Time” if you don’t supply a timezone. That’s it. There is no screen output for this function. Here’s the function:

function Set-TimeZone {
  [CmdletBinding(SupportsShouldProcess = $True)]
  param( 
    [Parameter(ValueFromPipeline = $False, ValueFromPipelineByPropertyName = $True, Mandatory = $False)]
    [ValidateSet("Dateline Standard Time","UTC-11","Hawaiian Standard Time","Alaskan Standard Time","Pacific Standard Time (Mexico)","Pacific Standard Time","US Mountain Standard Time","Mountain Standard Time (Mexico)","Mountain Standard Time","Central America Standard Time","Central Standard Time","Central Standard Time (Mexico)","Canada Central Standard Time","SA Pacific Standard Time","Eastern Standard Time","US Eastern Standard Time","Venezuela Standard Time","Paraguay Standard Time","Atlantic Standard Time","Central Brazilian Standard Time","SA Western Standard Time","Pacific SA Standard Time","Newfoundland Standard Time","E. South America Standard Time","Argentina Standard Time","SA Eastern Standard Time","Greenland Standard Time","Montevideo Standard Time","Bahia Standard Time","UTC-02","Mid-Atlantic Standard Time","Azores Standard Time","Cape Verde Standard Time","Morocco Standard Time","UTC","GMT Standard Time","Greenwich Standard Time","W. Europe Standard Time","Central Europe Standard Time","Romance Standard Time","Central European Standard Time","W. Central Africa Standard Time","Namibia Standard Time","Jordan Standard Time","GTB Standard Time","Middle East Standard Time","Egypt Standard Time","Syria Standard Time","E. Europe Standard Time","South Africa Standard Time","FLE Standard Time","Turkey Standard Time","Israel Standard Time","Arabic Standard Time","Kaliningrad Standard Time","Arab Standard Time","E. Africa Standard Time","Iran Standard Time","Arabian Standard Time","Azerbaijan Standard Time","Russian Standard Time","Mauritius Standard Time","Georgian Standard Time","Caucasus Standard Time","Afghanistan Standard Time","Pakistan Standard Time","West Asia Standard Time","India Standard Time","Sri Lanka Standard Time","Nepal Standard Time","Central Asia Standard Time","Bangladesh Standard Time","Ekaterinburg Standard Time","Myanmar Standard Time","SE Asia Standard Time","N. Central Asia Standard Time","China Standard Time","North Asia Standard Time","Singapore Standard Time","W. Australia Standard Time","Taipei Standard Time","Ulaanbaatar Standard Time","North Asia East Standard Time","Tokyo Standard Time","Korea Standard Time","Cen. Australia Standard Time","AUS Central Standard Time","E. Australia Standard Time","AUS Eastern Standard Time","West Pacific Standard Time","Tasmania Standard Time","Yakutsk Standard Time","Central Pacific Standard Time","Vladivostok Standard Time","New Zealand Standard Time","UTC+12","Fiji Standard Time","Magadan Standard Time","Tonga Standard Time","Samoa Standard Time")]
    [ValidateNotNullOrEmpty()]
    [string]$TimeZone = "Eastern Standard Time"
  ) 

  $process = New-Object System.Diagnostics.Process 
  $process.StartInfo.WindowStyle = "Hidden" 
  $process.StartInfo.FileName = "tzutil.exe" 
  $process.StartInfo.Arguments = "/s `"$TimeZone`"" 
  $process.Start() | Out-Null 
} # end function Set-TimeZone

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.

Function: New-PSUpdateHelpScheduledTask – Auto Update PowerShell Help

March 1st, 2013 2 comments

Powershell_logo-137x137Probably not of much value to people unless you build a fair amount of servers and have it scripted, but…

One thing you want to do with servers running PowerShell v3.0, such as Windows Server 2012, is to regularly run Update-Help from a PowerShell session to update the help files PowerShell uses for various modules. But this is something that can be easily forgotten, and, who wants to manually do a recurring task, anyways?

Here is a function you can toss in your server build script to create a scheduled task that will automatically update PowerShell help every day at 7pm. The task runs as ‘system’, so we don’t have to worry about storing credentials. Here’s the function

function New-PSUpdateHelpScheduledTask {
	[CmdletBinding(SupportsShouldProcess = $true)]
	param()
	BEGIN{
		[string] $TaskArguments = '-NoProfile -Command "& {Update-Help -Force; Start-Sleep -Seconds 5; Get-ChildItem $env:temp -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.PSIsContainer -eq $True -and $_.GetFiles() -match ''.cab''} | Remove-Item -Recurse -Force}"'
		[string] $TaskProgram =  "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
	}
	PROCESS{	
		$TaskAction = New-ScheduledTaskAction -Execute $TaskProgram -Argument $TaskArguments
		$TaskTrigger = New-ScheduledTaskTrigger -Daily -At 19:00
		Register-ScheduledTask -TaskName "Update PowerShell Help" -Action $TaskAction -Trigger $TaskTrigger -RunLevel Highest -User "system" -Description "Automatically updates PowerShell help." | Out-Null
	}
	END {
		Remove-Variable TaskAction
		Remove-Variable TaskTrigger
	}
} # end function New-PSUpdateHelpTask

and merely call it using

New-PsUpdateHelpScheduledTask

You’ll notice a new scheduled task in Task Scheduler, and we see that it completed successfully.
Update PowerShell Help
Just a couple of lines of code and we’ve removed the need to manually run Update-Help on a server.

Hope this helps!

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.

Why Use PowerShell? A Response

February 13th, 2013 2 comments

Powershell_logo-137x137My friend and colleague Patch Charron wrote an interesting article on why do we use PowerShell. In that article, Patch mentions that he wondered why he could spend more time writing and testing a script than if he just performed the scripted tasks manually. He gives pretty good reasoning.

I was on a project last year where I was tasked with automating the installation of some Exchange 2010 servers for a good-sized financial client. This was to ensure that the servers were each deployed in a consistent, repeatable way. One of the problems we ran into was that the servers were prepped for our build by different people from time to time. And even though the client had a “standardized process”, we received the servers in many different configurations. So I devised a prerequisite script that would verify each of the required settings and produce an output to both the screen and text file. It would identify each parameter, it’s required setting, and it’s actual configured setting. Settings such as server name, paging file, DNS domain suffix & search list were all issues that would vary depending on who would build the base server. The client’s procedures for resolving some of these problems would often take a considerable amount of time. So, I wrote another script to actually fix the problems that a script could fix. This was then implemented in the beginning of the server build framework. Two CSV files and 3,000+ lines of PowerShell, and I was able to build 163 Exchange servers, including more than 40 in one day. From a domain joined server to provisioned SAN storage, configured NICs, prerequisites installed, Exchange installed and configured, and more. Repeatable steps that the client could use going forward if needed. Every task, from copying the source files, scripts, tools, and others was automated.

My point is that PowerShell was not only able to automate a time-consuming, complex series of steps for building servers, but also able to identify and resolve nearly any issue that could either block the installation, or cause problems down the road.

Another issue PowerShell was able to resolve for a client was to streamline the employee onboarding and offboarding process. Onboarding employees would require creating an Active Directory account, and Exchange mailbox, a Lync account, SharePoint sites, etc. In larger organizations, these tasks are often performed by different groups of people. Many of these processes can (and were) automated. Create a user account in a certain OU (or in my case any OU that contained the word “employees”), and PowerShell can automatically mailbox, Lync, and SharePoint enable the user. And logic was built in to handle configuration of things like Lync policies, etc. This allows administrators to spend more time working on more important issues, and less time doing mundane tasks.

Microsoft isn’t the only company that builds PowerShell into its products. Other companies like Netapp and Kemp have built it into their products or have it as an option. This is key as you can us it to perform provisioning, maintenance, and disaster recovery.

So – back to Patch. Spending more time to write and test the script than to manually do the task might be tough, especially for internal staff that might not have to do that task, like building Exchange servers, again for years. But the rewards are great when you have an infrastructure that’s running more smoothly. If you’re a consultant, then scripting common tasks can help you as you can more quickly deploy solutions on future projects. Your clients will appreciate that.

I’ve seen it said many times that you either learn PowerShell, or find another line of work. I believe that to be a true statement. There are a truckload of great resources online to help you jump into PowerShell, find code examples, and broaden your skill set. Good luck!

Categories: PowerShell Tags:

Finding a Domain Controller Within the Same AD Site via PowerShell

November 7th, 2012 1 comment

Powershell_logo-137x137In Exchange Management Shell and Lync Server Management Shell, you can target many cmdlets at specific domain controllers. This is crucial, especially in larger environments, if you need to make sure AD replication delays aren’t going to cause issues. An example is enabling a user for Lync using Enable-CsUser, then trying to use Set-CsUser or Grant-CsExternalAccessPolicy. The second will fail if it sends it to a different domain controller than the first, and replication hasn’t completed. So, the -DomainController switch can be used. Just send each command to the same DC, and even in rapid succession, you’ll succeed.

However, if you’re reusing your scripts or functions, especially in different environments, you have to find a valid DC in same AD site, put that into the script/function, and go. What a waste of time!

We can streamline the process with just a couple lines of code. First, we use Get-WMIObject to retrieve info on the local computer.

[object]$ComputerInfo = (Get-WMIobject -class "Win32_NTDomain" -namespace "root\CIMV2")

Next, we assign a variable, $ADSite, to the site name returned from the first line

[string]$ADSite = $ComputerInfo[1].ClientSiteName

Then we get a list of DCs in that same site

$DCsInSite = (Get-ADDomainController -Filter {Site -eq "$ADSite"})

And lastly, we randomly pick a DC from that list

[string]$QueryDC = ($DCsInSite | Get-Random).name

$QueryDC can now be used in your code, such as

Enable-CsUser [user] -RegistrarFQDN [fqdn] -SipAddressType [SIP address type] -DomainController $QueryDC

And that’s it. The only real requirement here is that the ActiveDirectory module be loaded, so that the Get-ADDomainController cmdlet works. This is easy:

Import-Module ActiveDirectory

In its entirety, here is the code:

Import-Module ActiveDirectory
[object]$ComputerInfo = (Get-WMIobject -class "Win32_NTDomain" -namespace "root\CIMV2") 
[string]$ADSite = $ComputerInfo[1].ClientSiteName
$DCsInSite = (Get-ADDomainController -Filter {Site -eq "$ADSite"}) 
[string]$QueryDC = ($DCsInSite | Get-Random).name

 

Function: Set-AdminUser – Clear AdminCount and Enable Security Inheritance

October 22nd, 2012 25 comments

Powershell_logo-137x137Description

While migrating some users during a Lync migration, I needed to disable users for Lync in one forest, and enable them in another. I ran into a problem where many users in the legacy forest had adminCount set to 1, and security inheritance disabled. The problem is that my account didn’t have rights to disable them in Lync, and I was getting an access denied error. This is common when the user is/was a member of a protected group. I won’t go into the background of adminCount, as it’s well documented.

I knew that as the migration progressed, we would identify more users where this was the case. So I wanted to find a better way of dealing with these. There are several ways of finding users with adminCount set using PowerShell, including

([adsisearcher]"(AdminCount=1)").findall()

and using the ActiveDirectory PowerShell module via

Get-ADuser -LDAPFilter "(admincount=1)" | Select-Object name

and we can look at groups, too, using

Get-ADgroup -LDAPFilter "(admincount=1)" | Select-Object name

Turns out that many of the users (1000+) were no longer members of protected groups, what’s often referred to as Orphaned AdminSD Objects. So we could clear adminCount and enable security inheritance. But doing this manually on 1000+ users isn’t something that any of us wanted to spend time doing.

We can clear adminCount with a one-liner:

Get-AdUser [user name] | Set-AdObject -clear adminCount

But that doesn’t take care of security inheritance, which is the real culprit in my issue. We can use dsacls:

$User = [ADSI] $_.Path
dsacls $User.distinguishedName /p:n

Unfortunately, this involves multiple steps in native PowerShell. So, a function was born.

Set-AdminUser takes input from either the $UserName parameter, or via the pipeline, and clears adminCount, then enables security inheritance. It can process a single user or multiple, such as with the output of Get-ADGroupMember.

Syntax

Set-AdminUser [[-UserName] ] [-WhatIf] [-Confirm] []

Examples

Set-AdminUser [user name]
Get-AdGroupMember [group name] | Set-AdminUser

Code

Here is the function to copy. You can also download it in the download section below.

function Set-AdminUser	{  
	<# 
	.SYNOPSIS
			Clears adminCount, and enables inherited security on a user account.
	
	.DESCRIPTION
			Clears adminCount, and enables inherited security on a user account.
	
	.NOTES
	    Version    	      	: v1.0
	    Wish list						: 
	    Rights Required			: UserAdministrator
	    Sched Task Req'd		: No
	    Lync Version				: N/A
	    Lync Version				: N/A
	    Author       				: Pat Richard, Skype for Business MVP
	    Email/Blog/Twitter	: pat@innervation.com 	https://www.ucunleashed.com @patrichard
	    Dedicated Post			: https://www.ucunleashed.com/1621
	    Disclaimer   				: You running this script means you won't blame me if this breaks your stuff.
	    Info Stolen from 		: http://serverfault.com/questions/304627/powershell-script-to-find-ad-users-with-admincount-0
													: http://morgansimonsen.wordpress.com/2012/01/26/adminsdholder-protected-groups-sdprop-and-moving-mailboxes-in-exchange/
	
	.LINK     
	    
Function: Set-AdminUser – Clear AdminCount and Enable Security Inheritance
.INPUTS You can pipeline input to this command .PARAMETER UserName Create the scheduled task to run the script daily. It does NOT create the required Exchange receive connector. .EXAMPLE Set-AdminUser -UserName [user name] Description ----------- Clears the adminCount of the specified user, and enabled inherited security .EXAMPLE Get-AdGroupMember [group name] | Set-AdminUser Description ----------- Clears the adminCount of all group members, and enabled inherited security #> #Requires -Version 2.0 [CmdletBinding(SupportsShouldProcess = $True)] param ( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $True, Mandatory = $False)] [ValidateNotNullOrEmpty()] [string]$UserName ) Begin{ ## allows inheritance [bool]$isProtected = $false ## preserves inherited rules [bool]$PreserveInheritance = $true } Process{ [string]$dn = (Get-ADUser $UserName).DistinguishedName Set-AdObject -identity $dn -clear adminCount $user = [ADSI]"LDAP://$dn" $acl = $user.objectSecurity Write-Verbose $dn Write-Verbose "Original permissions blocked:" Write-Verbose $acl.AreAccessRulesProtected if ($acl.AreAccessRulesProtected){ $acl.SetAccessRuleProtection($isProtected,$PreserveInheritance) $inherited = $acl.AreAccessRulesProtected $user.commitchanges() Write-Verbose "Updated permissions blocked:" Write-Verbose $acl.AreAccessRulesProtected } } End{ remove-variable acl remove-variable UserName remove-variable isProtected remove-variable PreserveInheritance remove-variable dn remove-variable user } } # end function Set-AdminUser

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 – 10-20-2012 Set-AdminUser.v1.0.zip

Categories: PowerShell Tags: ,

Function: New-SignedScript – Easily Sign One or Many Scripts with Your Code Signing Cert

September 20th, 2012 No comments

Signs a PowerShell script with a code signing certificate.

Syntax

New-SignedScript [[-path] ] [-Verbose] [-Debug] [-ErrorAction ] [-WarningAction ] [-ErrorVariable ] [-WarningVariable ] [-OutVariable ] [-OutBuffer ] [-WhatIf] [-Confirm]

Detailed Description

One of the concerns about using a PowerShell script is that it often requires the user to change the Execution Policy on the machine the script is running on. This can cause security concerns, because when the Execution Policy is lowered, any script can run, including those with malicious intent. For more information on setting the Execution Policy, see Set-ExecutionPolicy.

Of course, you need a code signing certificate in order to sign scripts. Fellow Exchange MVP Mike Pfeiffer wrote an informative article, Obtaining a Code Signing Certificate and Signing PowerShell Scripts that covers using an internal Certificate Authority. Third party Certificate Authorities (CAs) such as Digicert also provide code signing certificates. I can’t recommend Digicert enough. I have both a standard code signing certificate and an Extended Validation code signing certificate.

But signing scripts manually can be a little cumbersome. This function gets the current code signing certificate, verifies it’s not expired, and then signs the script. The script will only sign .ps1 files, and will not attempt to sign a script that’s already signed.

Example

New-SignedScript -path [path to script]

such as

New-SignedScript -path .\myscript.ps1

You can also pipeline files to this function, for example:

Get-Item *.ps1 | New-SignedScript

Installation

Nothing special here. Once you have a valid code signing certificate installed, the function should work as designed.

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.4 – 09-08-2017 – New-SignedScript.v1.4.zip

v1.3 – 09-18-2016 – New-SignedScript.v1.3.zip

v1.1 – 06-10-2014 – New-SignedScript.v1.1.zip

v1.0 – 09-20-2012 – New-SignedScript.v1.0.zip

Changelog

See changelog for info on latest versions, including bug fixes, code tweaks, etc.

Categories: PowerShell Tags: , ,

Changelog: New-ExpiringCertificatesReminder.ps1

September 14th, 2012 No comments

This is the changelog page for New-ExpiringCertificatesReminder.ps1. 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 – 02-13-2017

  1. added parametersets to param block
  2. code optimization
  3. updated Remove-Variables to v1.1
  4. updated domain name info and comment help block

v1.2 – 01-27-2014

  1. -noprofile switch added to install routine
  2. minor code tweaks per best practices

v1.0 – 09-11-2012

  1. Initial version
Categories: PowerShell Tags: , ,