Tuesday, October 15, 2013

5000 page views


We hit a major milestone today! 5K page views was a goal that has now been met.

Just a quick note to mark the occasion. Now back to making posts about Powershell!

Wednesday, October 9, 2013

Everyday Powershell - Part 3 - Learn to use Powershell for adding windows features NOW!

This is the next part in an ongoing series about Powershell. You may have heard about how awesome Powershell is but have struggled to find ways to make it useful in your day to day work. That's what this series is going to address. It'll provide scripts and knowledge to address practical everyday problems!

The problem we're fixing today might seem a bit redundant, until you've used it a few times. We're doing a really basic admin function today, Adding Windows Features!

Yup that thing you can do with add roles and features wizard. It's a common task for admins after they've run up a fresh server. The cool thing with powershell is once you know what dependencies you need and get it sorted in powershell it's then never more than a few keystrokes away!

get-windowsfeature You can use this to figure out what a particular feature should be called and check if it's already installed

add-windowsfeature This will add the feature (or features) you've designated. You can add more than one feature just use a comma as a separator.

Again I've got to reiterate you'll only see the benefit of this if you start to use it. There is value trust me. Last month while building a test Sharepoint 2013 environment, I figured out all the prerequisites and got it all installed using Powershell. Once sharepoint was up a running I went to lunch. Came back and the Volume I'd provisioned my Virtual Machine on had been nuked by an admin (it was a test environment after all). It wasn't a problem at all though I just brought up another fresh OS and because I had spent the time to figure out the prerequisites I could run a little powershell to setup everything sharepoint would need! So there was plenty of time left over to torture that admin!

So to get you started with this here's some prerequisites already all sorted for you;

Exchange 2013
Add-WindowsFeature Desktop-Experience, NET-HTTP-Activation, RPC-over-HTTP-proxy, RSAT-Clustering, WAS-Process-Model, Web-Asp-Net, Web-Basic-Auth, Web-Client-Auth, Web-Digest-Auth, Web-Dir-Browsing, Web-Dyn-Compression, Web-Http-Errors, Web-Http-Logging, Web-Http-Redirect, Web-Http-Tracing, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Lgcy-Mgmt-Console, Web-Metabase, Web-Mgmt-Console, Web-Mgmt-Service, Web-Net-Ext, Web-Request-Monitor, Web-Server, Web-Stat-Compression, Web-Static-Content, Web-Windows-Auth, Web-WMI, RSAT-Clustering-CmdInterface

Sharepoint 2013
Add-windowsfeature Net-Framework-Features, Web-Server, Web-WebServer, Web-Common-Http, Web-Static-Content, Web-Default-Doc, Web-Dir-Browsing, Web-Http-Errors, Web-App-Dev, Web-Asp-Net, Web-Net-Ext, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Health, Web-Http-Logging, Web-Log-Libraries, Web-Request-Monitor, Web-Http-Tracing, Web-Security, Web-Basic-Auth, Web-Windows-Auth, Web-Filtering, Web-Digest-Auth, Web-Performance, Web-Stat-Compression, Web-Dyn-Compression, Web-Mgmt-Tools, Web-Mgmt-Console, Web-Mgmt-Compat, Web-Metabase, Application-Server, AS-Web-Support, AS-TCP-Port-Sharing, AS-WAS-Support, AS-HTTP-Activation, AS-TCP-Activation, AS-Named-Pipes, AS-Net-Framework, WAS, WAS-Process-Model, WAS-NET-Environment, WAS-Config-APIs, Web-Lgcy-Scripting, Windows-Identity-Foundation, Server-Media-Foundation, Xps-Viewer

Lync 2013
Add-windowsfeature RSAT-ADDS,Web-Server,Web-Static-Content,Web-Default-Doc,Web-Http-Errors,Web-Asp-Net,Web-Net-Ext,Web-ISAPI-Ext,Web-ISAPI-Filter,Web-Http-Logging,Web-Log-Libraries,Web-Request-Monitor,Web-Http-Tracing,Web-Basic-Auth,Web-Windows-Auth,Web-Client-Auth,Web-Filtering,Web-Stat-Compression,Web-Dyn-Compression,NET-WCF-HTTP-Activation45,Web-Asp-Net45,Web-Mgmt-Tools,Web-Scripting-Tools,Web-Mgmt-Compat,NET-Framework-Core,NET-HTTP-Activation,Desktop-Experience,Windows-Identity-Foundation,Telnet-Client,BITS 


SCCM 2012
Add-windowsfeature "BITS","BITS-IIS-Ext","BITS-Compact-Server","RDC","WAS-Process-Model","WAS-Config-APIs","WAS-Net-Environment","Web-Server","Web-ISAPI-Ext","Web-ISAPI-Filter","Web-Net-Ext","Web-Net-Ext45","Web-ASP-Net","Web-ASP-Net45","Web-ASP","Web-Windows-Auth","Web-Basic-Auth","Web-URL-Auth","Web-IP-Security","Web-Scripting-Tools","Web-Mgmt-Service","Web-Stat-Compression","Web-Dyn-Compression","Web-Metabase","Web-WMI","Web-HTTP-Redirect","Web-Log-Libraries","Web-HTTP-Tracing","UpdateServices-RSAT","UpdateServices-API","UpdateServices-UI"

One thing to note is that some of these install the .net 3.5 framework... A odd quirk of Server 2012 is that these need to come from your install media for some reason. Just mount the ISO and hand this parameter to the command;
-Source D:\sources\sxs 

If you are doing a Sharepoint 2013 build check up Craig Lussiers post here http://gallery.technet.microsoft.com/office/DownloadInstall-SharePoint-e6df9eb8 it'll likely save you hours!

If you are getting into an SCCM install check out NicloaJ's post here http://www.scconfigmgr.com/2013/04/14/install-configmgr-2012-sp1-prerequisites-with-powershell/

For other System Center products Steve Beaumont has put in a lot of effort on documenting and aquiring prerequisites http://systemscentre.blogspot.com.au/2013/01/system-center-2012-sp1-setup-software.html

Wednesday, October 2, 2013

Everyday Powershell - Part 2 - The BOFH Speaks!

This is the next part in an ongoing series about Powershell. You may have heard about how awesome Powershell is but have struggled to find ways to make it useful in your day to day work. That's what this series is going to address. It'll provide scripts and knowledge to address practical everyday problems!

This script is a bit of fun that we can also use for learning! The everyday problem we're addressing here is the lack of funny in our days. I know you'll also be able to reuse the functions in the script for actual productive work but that shouldn't get in the way of the funny!

First step Fire up the Powershell ISE and paste this in, turn up your speakers, run the script and enjoy the funnies.
#requires -version 2
function Say-Text
{
    param ([Parameter(Mandatory=$true, ValueFromPipeline=$true)] [string] $Text)
    [Reflection.Assembly]::LoadWithPartialName('System.Speech') | Out-Null
    $object = New-Object System.Speech.Synthesis.SpeechSynthesizer
    $object.Speak($Text)
}

function download-file
{
    param ([string]$path, [string]$local)
    $client = new-object system.net.WebClient
    $client.Headers.Add("user-agent", "PowerShell")
    $client.downloadfile($path, $local)
}

$path = "http://www.cs.wisc.edu/~ballard/bofh/excuses"
$local = "c:\temp\temp.html"

download-file $path $local

$excuse = Get-Content $local | get-random
say-text "Today's excuse is... $excuse
"

Then save it as a .ps1 file somewhere sensible and look over Part 1 of this series for the low down on how to schedule this script to run on a daily basis!

Imagine it! You can have your Computer read out a BOFH excuse every day! This... THIS, is why I love Powershell.

Read on for a line by line breakdown of what is actually going on in the script!

Part 1
function Say-Text is actually a cool way to demonstrate functions and have a bit of fun with .net objects.

param ([Parameter(Mandatory=$true, ValueFromPipeline=$true)] [string] $Text) Essentially this is just setting up the parameters that can passed to the function. In this case we're just pulling in a string called $text it's mandatory and can be passed from the pipeline.

[Reflection.Assembly]::LoadWithPartialName('System.Speech') | Out-Null This loads System.speech so we can use it. The pipe to out-null simply prevents dumping this kind of thing to your console...
True   v4.0.30319     C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Speech\v4.0_4.0.0.0__31bf3856ad364e35\System.Speech.dll    

$object = New-Object System.Speech.Synthesis.SpeechSynthesizer This creates a new .net object called $object.
NOTE - Something cool you can try once you've got an object like this is piping to get-member like this;
[Reflection.Assembly]::LoadWithPartialName('System.Speech')$object = New-Object System.Speech.Synthesis.SpeechSynthesizer$object | get-member 
You'll get this useful list of all the methods and properties of the object.
$object.Speak($Text) Next up we simply called the object with the speak method and hand $text as the argument! There you have a talking PC!

Part 2
function download-file Next up is another function This one uses .net to pull down a file from a web server.

param ([string]$path, [string]$local) Same as parameters in the previous function but this time there are 2! You separate them with commas. We're pulling in $path and $local take note of these we'll need them later!

$client = new-object system.net.WebClient Setting up a .net object same as in the previous function but setting up a different kind of object this one is a webclient.

$client.Headers.Add("user-agent", "PowerShell") We've got to do a bit of work to this object before we can use it. Web servers get all funny if you don't give them a user-agent string.

$client.downloadfile($path, $local) This does the work and downloads the thing in $path and saves is in $local.

Part 3
So with the functions setup we move into the main bit of the script 

$path = "http://www.cs.wisc.edu/~ballard/bofh/excuses"
$local = "c:\temp\temp.html"

Recognize those variables? Yup we'll be using them right now!

download-file $path $local So we're calling the function setup earlier with the two variables we just created and set. 

$excuse = Get-Content $local | get-random This sets up a variable $excuse and pipes the content of the file we just downloaded to get-random... This will all become clear (if it's not already)

say-text "Today's excuse is... $excuse" Now we call the function we setup at the start and hands it a bit of text. See what it's doing?! Isn't Powershell cool?!

So this is just a quick example of something neat you can do with powershell. One thing to keep in mind is you can copy and paste those functions and use them in other scripts.

Let me know if you have any trouble with this script by leaving a comment below. Also there's a glaring inefficiency in this script so add a comment pointing out what it is if you can, and for even more kudos suggest a more efficient approach!

Lastly full credit for the say-text function should go to http://mckibbins.info/2013/01/21/powershell-text-to-speech/. 

Monday, September 30, 2013

When the Shell has too much power

We had an incident last Friday. Our production print queues went down for about 20 minutes

Our post incident report has this as the root cause

The following script was run with the variable $server set to PROD-PRINT-SERVER;
$printers = Get-WmiObject -class win32_printer -computername $server | where {$_.name -like "*Toshiba*"}
foreach ($printer in $printers)
{
    $printer.delete()
} 

This removed all Toshiba print queues on the server in question (the production print server). The $server variable should have been TEST-PRINT-SERVER.


Yup! That'll do it. It was all resolved pretty quickly, just restored the print server from the latest snapshot.

So when rolling out powershell to enthusiastic admins make sure to put the breaks on in the right places.

Our fix for this will be to change the ACLs for production print queues so that no-one has the rights to delete them. That way when the time comes to delete a queue we'll have the additional step of changing the ACL but that's better that being able to blow away production print queues with a goofed WMI query.

Anyone have any other thoughts on what could have prevented this situation? Leave us a comment!

Thursday, September 26, 2013

Everyday Powershell - Part 1 - Scheduling Scripts

This is the first part in a series about Powershell. You may have heard about how awesome Powershell is but have struggled to find ways to make it useful in your day to day work. That's what this series is going to address. It'll provide scripts and knowledge to address practical everyday problems! 

In order to really see the power of the shell you must experience what it can do for automation. To do that we'll have to setup a windows scheduled task.

First this is to make sure Powershell will run your script (set-executionpolicy remotesigned) this sets your execution policy to allow local scripts to run. But prevents remote scripts.

So to the actual work you need a scheduled task... you know how to do those right? Of course you do! You wouldn't be looking at a scripting blog if you didn't know that. (If you don't know and would like to, add a comment below and we'll make sure to do a post about that.)

On Server 2003 you can hand it one command with the powershell.exe and the script as an argument;
C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -PSConsoleFile "C:\Program Files\Microsoft\Exchange Server\bin\exshell.psc1" -command "& 'C:\scripts\MAHSCRIPT.ps1'" 

Set start in to;
C:\WINDOWS\system32\windowspowershell\v1.0



On Server 2008 you want to select the Action "start a program"
You'll get a box for the Program/Script;
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Then Add Arguments
-command "& 'C:\scripts\MAHSCRIPT.ps1' " 

Things to watch out for will be the credentials that the script will run as. Make sure it'll have access to whatever it needs. I usually test the script "Run As" the same account I'm planning to use in the scheduled task, just to be sure it won't break.

So the next thing you'll need is some kind of script to run everyday, if you don't have one tune in for the next post and I'll provide you with something awesome to run everyday!

Tuesday, September 24, 2013

Top 5 Powershell performance tips

Five quick tips to improve performance of powershell scripts. Don't take my word for them either, each tip links to an article written by people smarter than I am!

Don't use the += operator!
It's not appending to your array... It makes a copy of the original array adding the new data then deletes the old array. This won't scale well at all. Dave Wyatt has come up with a great solution.
http://powershell.org/wp/2013/09/16/powershell-performance-the-operator-and-when-to-avoid-it/

Foreach-object can be more efficient than Foreach
When you use a foreach the entire collection of objects is loaded into memory first. foreach-object processes them one at a time. There's an interesting explanation here from Ketan Thakkar
http://social.technet.microsoft.com/Forums/en-US/e8da8249-ea91-4772-ae85-582a4b37425b/powershell-foreachobject-vs-foreach

Filter on the "Left Side" of the Pipe
If you are using the pipeline see if you can filter before piping to the where-object commandlet. Most commonly for me in get-wimobject or get-aduser. If you use a -filter well you won't have to bring the entire dataset down to be filtered on your client. Martin Pugh has done some interesting analysis on this kind of thing here
http://thesurlyadmin.com/2012/10/08/powershell-and-string-searches/

Use Multiple Threads where you can
Can the job be run in Multiple threads? If so it'll be worth the effort to get your head around the complexities of Multithreading. Powershell has the concept of jobs. Here's a pretty good example;
http://stackoverflow.com/questions/16360019/how-do-i-add-multi-threading

Measuring performance of your Scripts
So how do we prove these tips will actually improve performance? Well we can use measure-command! It'll time how long it takes for a particular command to execute. So we can use this to benchmark and work on optimizing our scripts performance. Documentation is here;
http://technet.microsoft.com/en-us/library/ee176899.aspx

Tuesday, September 17, 2013

Copy files modified in the last X days with Powershell

A friend asked on the weekend for some powershell that would allow him to copy files modified in the last 7 days to a new machine. It's a great opportunity to showcase the powershell pipeline feature!
get-childitem "c:\Source" | where-object {$_.LastWriteTime -gt (get-date).AddDays(-7)} | Copy-Item -destination "C:\Target"

This one liner will work for files in a folder (it'll get messy if we recurse though, there's a fix for that coming later).

See the pipe command '|' this used to pass the output of one command as input to another command.

  1. So we get-childitem (which is the powershell name for DIR) 
  2. then we pass that to where-object with a bit of code that looks at the creationtime property. the little $_. refers to the "next object in the pipeline" so we can hook into the properties of all the obejcts we found with get-childitem, perform an evaluation on the creation time,
  3. Then the object is passed to copy-item

Now if we want to be able to recursively do this we'll need to be a little more tricksy!

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
$source = "c:\users\benh\documents"
$target = "c:\test"

$files = get-childitem $source -Recurse
foreach ($file in $files)
{
    if ($file.LastWriteTime -ge (get-date).AddDays(-7))
    {
        $targetFile = $target + $file.FullName.SubString($source.Length)
        New-Item -ItemType File -Path $targetFile -Force
        Copy-Item $file.FullName -destination $targetFile
    }
}

This process uses a foreach loop which is similar to the pipeline in that it'll iterate through each item. But it allows us to use a complete script block which gives us a bit more power at the cost of performance. The main difference here is;
  1. We use an IF statement to evaluate how old the file is
  2. We figure out what the target path should be by dropping the $source directory from the files full name
  3. We create a new-item with the path we've established
  4. Then it's just copying the item as normal
Feel free to add and comments or if you've got a more elegant way of solving this problem let us know!