Wednesday, October 30, 2013

Everyday Powershell - Part 6 - Replacing Printers

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!

When the print server migrations happen they can be a big pain. Our last migration was breeze for us and the users thanks to Powershell!

This script requires a little more leg work than usual in that you've got to setup a file that can be referred to to map the old print queues to the new ones. Once you've got that though, you're a loop and a few WMI queries away from promised land of automagic printer migration!

The tricky part here is running the script as the user logs on... There are many ways to do this, and there's probably a good post on the subject coming.

$replacements = import-csv "\\SOMENETWORKSHARE\printers.csv"
$printers = Get-WmiObject -class win32_printer # obviously you can tart up this query with a bit of filtering e.g. | where {$_.name -like "*Toshiba*" -and $_.systemname -eq "\\OLD PRINT SERVER" -and $_.name -notlike "*SOMESITE*"}
$default = Get-WmiObject -class win32_printer -Filter "default = $true"

foreach ($printer in $printers)
{
    $printername = ""
    $newprinter = ""
    $newprinterpath = ""
    if ($printer.systemname -ne "\\NewPrintServer")
    {
        $printername = $printer.name
        $newprinter = $replacements | where {$_.old -like "*$printername*"| select -expandproperty new
        $newprinterpath = '\\NewPrintServer\'+$newprinter
        write-host "Replacing... $printername with $newprinterpath"
        (new-Object -com WScript.Network).AddWindowsPrinterConnection($newprinterpath| out-null
        $printer.delete()
    }
}

$printername = $default.sharename
$defaultprinter = $replacements | where {$_.old -like "*$printername*"| select -expandproperty new
(get-wmiobject -class win32_printer | where {$_.sharename -like "$defaultprinter"}).setdefaultprinter() 


This worked really well for our users. The tricky part was mapping the default printer to the new queue. I just ended up running it through the same logic as I'd setup for the replacements.

This is also another good example of a foreach loop in action.

The CSV needs two colums "old" and "new" this should contain the sharenames of the printers you are migrating... Like this;

old,new
old-hp,new-hp
old-samsung,new-samsung

Post a comment if you need any clarification on how to use this.

Wednesday, October 23, 2013

Everyday Powershell - Part 5 - Terminating AD User Accounts

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!

So today we've got a script that let's us dig into handing arguments from the command line, if statements and a little bit of active directory!

Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin

#make sure we've got a user to work with
$user = $args[0]
if ($args.count -lt 1)
{
    $user = read-host "Please enter the username"
}

# make sure we've got the right user, not sure if out-gridview -passthru is the best option.
$users = get-aduser -Filter "samaccountname -like '$user*'"
if ($users.count -gt 1)
{
    Write-host "More than one user found. Who are we after? (highlight a name and press enter)"
    $user = ($users | Out-GridView -PassThru).samaccountname
}

Set-Mailbox -Identity $user -HiddenFromAddressListsEnabled $true

# Removed mailbox and user account
$groups = Get-ADuser $user -Properties memberof | select -ExpandProperty memberof
$groups | Remove-ADGroupMember -members $user
Disable-ADAccount $user
$disabledname = (Get-ADUser $user -Properties displayname).displayname + " (Disabled)"
Set-aduser $user -DisplayName $disabledname

# Tell the Manager
$manager = Get-ADUser $user -Properties manager |  %{(Get-AdUser $_.Manager -Properties EmailAddress).EmailAddress}
$subject = "$user has been disabled."
$body = "HI, $manager, $user has left the organisation. We've disabled the user account."
Send-MailMessage -From "helpful@helping.com" -to $manager -Subject $subject -Body $body -SmtpServer smtp.gmail.com

Let's run through this section by section
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin
This line just loads a snapin for exchange it's nifty how powershell can be extended like this.

$user = $args[0]
So you can see we're setting $user to this $args[0] thing... this is how we tell our script to accept the "arguments" that came after the name of our script. If you save the whole script to c:\temp\terminate-user.ps1 you could call the script with an argument like this;
C:\temp\terminate-user.ps1 USERNAME
This would run the script with $user set to the argument "USERNAME". The [0] is just refering to the first argument handed to the script (yup you can hand more than one!)

if ($args.count -lt 1)
{
    $user = read-host "Please enter the username"
}
This chunk is our first if statement. We're checking the .count of $args to see if there is Less Than (-lt) 1. IF there aren't any $args we ask the operator for a username using read host. IF $args.count is 1 or greater powershell knows to skip over the script block defined by the curly braces { and }

$users = get-aduser -Filter "samaccountname -like '$user*'"
Doing a similar thing here with $users... We're going out to Active Directory and finding all the accounts with a name like $user 

if ($users.count -gt 1)
{
    Write-host "More than one user found. Who are we after? (highlight a name and press enter)"
    $user = ($users | Out-GridView -PassThru).samaccountname
}
Now we're checking $users to see if it has a .count Greater Than (-gt) 1. If it does we throw a warning and give the operator a grid of users to pick from. We do this so we can be sure that we're disabling the user we mean to.

Set-Mailbox -Identity $user -HiddenFromAddressListsEnabled $true
Hiding the user from the Global address list.

$groups = Get-ADuser $user -Properties memberof | select -ExpandProperty memberof
$groups | Remove-ADGroupMember -members $user
Removing the user from all AD groups by creating $groups populated with all the groups $user is a member of then piping $groups to remove-adgroupmember... Isn't powershell cool!?

Disable-ADAccount $user
$disabledname = (Get-ADUser $user -Properties displayname).displayname + " (Disabled)"
Set-aduser $user -DisplayName $disabledname
This next bit disables the user... and then we add (Disabled) to the display name. This way anyone snooping through AD won't freak out when they discover staff that have departed. 

$manager = Get-ADUser $user -Properties manager |  %{(Get-AdUser $_.Manager -Properties EmailAddress).EmailAddress}
$subject = "$user has been disabled."
$body = "HI, $manager, $user has left the organisation. We've disabled the user account."
Send-MailMessage -From "helpful@helping.com" -to $manager -Subject $subject -Body $body -SmtpServer smtp.gmail.com

Lastly we have a little value add for the users manager. We send them a little email so they can be sure the account has been disabled. You'll have to jiggle that send-mailmessage command so it works in your environment.

Monday, October 21, 2013

I knew this was coming...

How long is it from the time the boss becomes aware of a particular capability until we are asked to use it across the whole organisation...?

Not long ;D

http://occasionalutility.blogspot.com.au/2013/07/deleting-emails-from-users-mailboxes.html

Some bit of communication got out of the gates too early and the Boss wanted a "recall"

It was really rewarding to be able to say "No problem Boss! We've got a script for that." Clickety Click, she's all fixed!

My advice? Start building your arsenal of awesome scripts as soon as you can.

Wednesday, October 16, 2013

Everyday Powershell - Part 4 - Chore List

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!

I found this in my travels a while back;
http://www.reddit.com/r/PowerShell/comments/1n8w5w/assigning_daily_family_chores/

It's not my script but I have made a couple of changes for the sake of compatibility. I've picked it out because it's a fantastic idea and a great opportunity to look at two important functions in powershell. import-csv and foreach.

Have a look at the script;

# requires -version 2
# Chores.ps1
# Parses chores.csv, randomly assigns the chores and sends an email to each person
# Retrieves family members from family.csv. First person listed in family.csv receives full summary email

$today = (get-date).DayOfWeek
$chores = import-csv chores.csv | where-object {$_.$today -eq 1| select Chore, Weight
$chores = $chores | get-random
-count ($chores.count) | sort-object -property Weight -Descending
$family = import-csv family.csv
$choremsg = @()
$smtpuser = "person@gmail.com"
$smtppassword = "notapassword"

write-host Generating chores list for $Today

# Create first column in $chorelist array to hold the names of who is doing the chore
foreach ($familymember in $family)
{
    $choremsg = $choremsg + ("`r`n" + $familymember.person + "'s Chores for the day:`r`n-------------------------------`r`n")
}

$choreworker = 2
foreach ($chore in $chores)
{
    $choremsg[$choreworker - 1] = $choremsg[$choreworker - 1] + $chore.chore + "`r`n"
    $choreworker++
    if ($choreworker -gt $family.count) { $choreworker = 1 }
}

$choremsg = $choremsg + ("`r`nPlus other duties as assigned `r`n")

$choremsg
$choremsgsubject = ("Chores for " + $today)

$smtpconn = new-object system.net.mail.smtpClient
$smtpconn.host = "smtp.gmail.com"
$smtpconn.port = 587
$smtpconn.EnableSsl = $true
$smtpconn.Credentials = new-object system.net.networkcredential($smtpuser,$smtppassword)
foreach ($familymember in $family)
{
    $smtpconn.send('person@gmail.com',$familymember.email,$choremsgsubject,$choremsg)
}

It's a cool bit of powershell that automates dishing out household chores to the family. You need to setup 2 CSV files for this to work.

Chores.csv Chore,Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Weight Feed dog,1,1,1,1,1,1,1,1 Feed cat,1,1,1,1,1,1,1,1 Clean Main Floor Bathroom,0,0,1,0,0,0,1,3 Family.csv Person, Email Dad,dad@gmail.com Mom,mom@gmail.com kid1,kid1@gmail.com kid2,kid2@gmail.com

Once you've got those CSV files we can use the import-csv function to pull them into powershell as objects that we can use!

Give it a try! Fill $family with the content of the CSV
$family = import-csv family.csv

Cool now just type;
$family

You'll see that your CSV is now a powershell object with properties corresponding to the column headers in your CSV.

Now try
$family.person

This'll just give you all the people. Now try
$family.email

This'll output just the emails.

The other thing to note with this script is the foreach command. Basically this is saying for each thing in a group of things do something. This kind of loop is used all the time in powershell and it's worth taking some time to get your head around.

This script is the essence of Everyday Powershell! Scripting with powershell shouldn't be intimidating, it's automation made awesome! The best way to learn is to just get stuck in and start solving problems and I can't think of much better problems to solve than eliminating arguments about who does what around the house!

So big thanks to Reddit user MessageforyouSir for this little gem. It's a been great excuse to play with and learn about import-csv! Oh and remember if you want to schedule a script check out Part 1 of this series.

Security Note - The security aware among you will note that the script requires that you hard-code a password in plain text. This is generally not considered good practice and wouldn't be necessary if you run you own SMTP server that allows relaying. There's probably a post or two just in managing and storing and using credentials in this way. You'll have to stay tuned for those.







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] $TextEssentially 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($TextNext 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]$localSame 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, $localThis 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/