Voice Spoken Weather Report

Here is a Powershell script that is fun, and useful if you like to be able to get a spoken weather report quickly.  It uses the SAPI com object in Windows to convert text to voice. If you have more than one TTS engine on your PC, you will be able to modify it under the Windows Control Panel.

To start with you will need to create a function that will be used to convert text to voice.

function say

{

$Voice = new-object -com SAPI.SpVoice #Make a voice object using the com object.

$Voice.Speak( $Args[0], 0 )|out-null

}

The second part of this script comes from /\/\o\/\/. You’ll find a detailed post on how to connect to a web-service to capture weather information.  That is located here:

http://thepowershellguy.com/blogs/posh/archive/2009/05/15/powershell-v2-get-weather-function-using-a-web-service.aspx

What I’ve done is to put his work into a function that allows you to select a country or city based on command line parameters, and then speak the results over your computer speakers. If you select the -help parameter and use the -country countryname parameter, all of the cities for your country will be selected.

Here is that function:

Function Get-Weather ([switch]$help,$city,$country,$filter = ”)

{

$weather = New-WebServiceProxy -uri http://www.webservicex.com/globalweather.asmx?WSDL

if ($help)

{

write-host “Starting help.”

$xml = [xml]$weather.GetCitiesByCountry($Country)

$xml.NewDataSet.table | sort city | Out-GridView

}
else

{

([xml]$weather.GetWeather($City,$country)).CurrentWeather

}

}

Now that the functions are out of the way, here is the “main” portion of the script.

#Main
#Determine if help switch is active.

switch ($help)

{

{$_ -eq $true }

{

get-weather -help -country $country

break

}

default

{

if ($full)#This is the full text output. No audio.

{

Get-Weather  -country $country -city $city

}

else #Audio output, streamlined for quicker information.

{

$currentWeather = get-weather -city $city

Write-Host $currentWeather

$currentTemperature = $currentWeather.temperature

$currentTemperature = $currentTemperature.split()

$currentSkyConditions = $currentWeather.SkyConditions

$wrsentence = “Here is the weather information you needed. In ” +  $city + ” the temperature is ” + [int]$currentTemperature[1] + ” degrees fahrenheit, and it is ” + $currentSkyConditions

say $wrsentence

}

}

}

I’ve used the default city of Memphis that I set up in the parameters, but it works just as well if I used the command line parameters.

I’ve saved all of this in a script called get-weather.ps1

Here is a sample using  Quebec, Canada

.\get-weather  -country Canada -city Quebec

If  I am not sure of all of the city names that are available for Mexico, then I can type:

.\get-weather  -country Mexico -help

This will open up an out-grid view with all of the cities available in Mexico.

Since I set Memphis as the default city in the parameters, just typing the following will give me local weather information.

.\get-weather.ps1

If I want just screen output for a city, I can use something like the following:

.\get-weather -full -country canada -city Quebec

Give this a try and see if it works. Let me know if you have any questions.

Thanks,

Patrick

Advertisement

User Home Folder Size and other Information (without Quest)

Frequently during my daily work I need to gather information on users that are contained in our Active Directory listing.  In a previous blog post, I had a script which does this work using a Quest commandlette.  Some environments don’t allow this, so a method to gather user data without using Quest is helpful to have available.
This script is used to quickly gather information on a users home folder, SAM account name, their email address, and the size of their home folder.

I use this a lot when I am moving users home folders from one server to another. To save time I frequently have the line that gathers home folder size remarked out with #.

The user account names that I am searching for are contained in a text file called
accounts.txt. This file is contained in the same folder as this script. The output is sent to the screen as well as a log file called output.csv.

#************************************************************

$userArray = @(“SAMID,HomeDirectory,EmailAdress,HomeFolderSize”)
$allUsers = gc .\accounts.txt
$tempArray = @()
function logfile($strData)
{
Out-File -filepath output.csv -inputobject $strData -append
}
function getAccountInfo
{
$strName = $currentUser
$strFilter = “(&(objectCategory=User)(samAccountName=$strName))”
#Get User AD info
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.Filter = $strFilter
$objPath = $objSearcher.FindOne()
$objUser = $objPath.GetDirectoryEntry()
[string]$folder = $objUser.homeDirectory
[string]$email = $objUser.mail
[string]$samID = $objUser.sAMAccountName
[string]$folderSize= getFolderSize($objUser.homeDirectory)
#$objUser.memberOf

$result = “$samID,$folder,$email,$folderSize”
$result #This causes the output to steam out, and be piped as the return from the function.
$folderSize = $null
$fs = $null
}
function getFolderSize($strPath)
{
$fs = New-Object -comobject Scripting.FileSystemObject
#Check validity of $strPath
if ($fs.FolderExists($strPath))
{
[double]$tempSize = ($fs.GetFolder($strPath).size) / 1024 / 1024
$tempSize = ‘{0:N}’ -f[double]$tempSize
$tempSize
}
else
{
$tempSize = “Bad folder path!”
$tempSize
}
}
$header = “SAMID,HomeDirectory,EmailAdress,HomeFolderSize”
Out-File -filepath output.csv -inputobject $header
foreach ($currentUser in $allUsers)
{
$tempOutput = getAccountInfo $currentUser
$tempOutput
$userArray += [string[]]$tempOutput
logfile($tempOutput)
}

#************************************************************
Let me know if you have any questions about this, or if it is helpful.

Thanks
Patrick

User Home Folder Size and other Information (with Quest)

Here is a function that can be used to quickly gather folder information about a user’s home folder.

There is one stipulation.

For this to work you must have the Quest Active Directory Snap-In configured for your Powershell session.
This will apply to users contained within an Microsoft Active Directory structure.
I have used the “^” in place of the “select-object” command. This is an alias that I use to make typing much faster. It is a symbol that I have never had a conflict on.

I have called the function GQUF. This is short for Get-QADUserFunction, but you may call it whatever you like of course.

Here is the syntax of the command. There are three options that are available when this command is run with the second command line switch.

“GQUF userid –groups” or

“GQUF userid -explorer” or

“GQUF userid –size.”

The –groups switch will detail all of the active directory groups in which the member is included.

The –explorer switch will open an explorer window pointed at the user’s home folder.

The –size switch will detail the total size of the user’s home folder.

Here is the code. See a screen shot at the bottom.

#*******************************

#This function looks up a user home drive and home directory

#Uses get-qaduser

function gquf

{

$UserID = $Args[0]
$Domain = $Args[1]

$result = Get-QADUser $Args[0] | Select-Object SamAccountName, homedirectory, homedrive, email, displayname # | ft -autosize

#$result | ft -autosize

Write-Host “Display Name:” $result.displayname -foregroundcolor green

Write-Host “Email Address:” $result.email -foregroundcolor green

Write-Host “HomeDir:” $result.homedrive $result.homedirectory -foregroundcolor green

Write-Host “”

Write-Host “Permissions for “$result.homedirectory -foregroundcolor Yellow

get-aclf $result.homedirectory

switch ($Args[1])

{

{$_ -eq “-groups”}

{

write-host “Member Of:”

(Get-QADUser $Args[0] | ^ memberof).memberof | sort

}

{$_ -eq “-explorer”}

{

explorer $result.homedirectory

}

{$_ -eq “-size”}

{

Write-Host “Calculating the size of the homefolder…” -foregroundcolor red

$fs New-Object -comobject Scripting.FileSystemObject

$tempSize $fs.GetFolder($result.homedirectory).size/1024/1024

$tempSize ‘{0:N}’ -f [double]$tempSize

Write-Host “$tempSize MB”

}

}

}
#*******************************

Here is the screen shot for the –size switch. Sensitive information has been blocked out.

Thank You,

Patrick

Creating a Folder Named After a Date

I like to create folders on the fly for logging  purposes, as well as for keeping track of re-occurring actions, like scanning for disk usage on a given date.

The following PowerShell command is useful for creating a folder with a name in the format YYYYMMDD.

$folderName = “folder1_” + (Get-Date -uFormat  “%Y%m%d”)
This command makes a folder call folder1_20110226.

Another technique is do make the folder, and assign the date to the name all at once.

md (“folder2_” + (Get-Date -uFormat  “%Y%m%d”)).

Below you will see both techniques used, and then the old DIR command just to show that they were created successfully.

Make Folders with Date Names.

Instead of the DIR command I could have used the Powershell commandlette Get-ChildItem folder*, and it would have worked just as well.  I like DIR because I am use to it, and because it is less typing.

That’s all for this entry. Have a nice day.

Thanks,

Patrick

Retrieving Shares in Powershell with WMI

Powershell one liners are a great way to work with Windows Management Instrumentation (WMI).  One of the WMI features I use the most is Win32_Share. It is a fast and easy way to retrieve share information.

In this blog entry I would like to explore the capabilities of WMI by developing a WIN32_Share utility. To begin let’s look at the most simple command available.

Get-WmiObject win32_share

Basic WMI Win32_Share Command

You’ll see from the image above that we get back three types of information from this WMI query. Name, Path, and Description. It may not be readily visible, but we also get back several types of shares.  Above we see administrative shares, printer, and shares, and regular file shares. As a system administrator that is interested in managing the files shares available to my user I want to work with only file shares now.  We can add some syntax to filter the type of share that is returned. To do that we need to know the share type.

Here is a modified version of the basic command we used above:

Get-WmiObject win32_share | Select-Object name, path, description, type | Format-Table -autosize

I used the Powershell commandlette select-object to request four specific properties to be returned from the WMI query. They are name, path, description, and type.  Also, I’ve added the text “Format-Table -autosize” to make it all fit neatly on the screen. Here is the result of the query:

GWMI WIN32_Share with Select-Object

Now to make it even more useful, we only want the shares that would be accessed by our users. They don’t need access to the admin shares (type 2147483648), or the shared printer (type 1). To filter on the type property we can use the Powershell commandlette where-object:

Get-WmiObject win32_share | Select-Object name, path, description, type | Where-Object { $_.type  -eq ‘0’}| Format-Table -autosize

GWMI Win32_Share with Type Filtering

Finally we can use the sort-object commandlette to sort the WMI query based on any property we want:

PS>$ Get-WmiObject win32_share | Select-Object name, path, description, type | `>> Where-Object { $_.type  -eq ‘0’}| Sort-Object path | ft -autosize>>

WMI Win32_Share with Sort

One great feature of Powershell version 2.0 is the out-gridview commandlette. It allows you to sort and filter using a dotnet gridview. Here is the command a screen capture of the output. Notice how the actual command is much short as we are filtering and sorting in the resulting gridview object:

Get-WmiObject win32_share | Select-Object name, path, description, type | Out-GridView.

WMI Win32_Share with Out-GridView

So, that’s it for now. Next time we will expand this further to see how we can pull back the file shares from multiple servers at once.

Text To Voice Conversion

Here is a little script that is fun to use, and could be useful in some applications.

I put this in my profile, so that I am greeted with the data whenever I start a Powershell session.

$today = [DateTime]::Now # Get Current time and date.
$Voice = new-object -com SAPI.SpVoice #Make a voice object using the com object.
$Voice.Speak( “Good Day Patrick!”, 1 )#1 causes function to continue w/o wating.
$day = $today.DayOfWeek #Determine the day.
$dayNumber = $today.Day #Determine the day number
$Voice.Speak( “Today is $day the $dayNumber”, 1 )

The only down side to this is the fact that there is no suffix on the $dayNumber value. A good way to work around that would be to create a switch statement.

Here is an example of how that could be implemented.

switch ($dayNumber)
{
19 {$properDay = “nineteenth” }
20 {$properDay = “twentieth” }
}

To implement this for the entire month, you would add the entire range of possible day numbers from 1 to 31.
Here is how the whole script would look.

$today = [DateTime]::Now # Get Current time and date.
$Voice = new-object -com SAPI.SpVoice #Make a voice object using the com object.
$Voice.Speak( “Good Day Patrick!”, 1 ) #The 1 parameter after the text causes the function to continue without wating.
$day = $today.DayOfWeek #Determine the day.$dayNumber = $today.Day #Determine the day number

switch ($dayNumber)

{
1 {$properDay = “first” }
2 {$properDay = “second” }
3 {$properDay = “third” }
4 {$properDay = “fourth” }
5 {$properDay = “fifth” }
6 {$properDay = “sixth” }
7 {$properDay = “seventh” }
8 {$properDay = “eigth” }
9 {$properDay = “ninth” }
10 {$properDay = “tenth” }
11 {$properDay = “eleventh” }
12 {$properDay = “tweflth” }
13 {$properDay = “thirteenth” }
14 {$properDay = “four-teenth” }
15 {$properDay = “fifteenth” }
16 {$properDay = “sixteenth” }
17 {$properDay = “seventeenth” }
18 {$properDay = “eighteenth” }
19 {$properDay = “nineteenth” }
20 {$properDay = “twentieth” }
21 {$properDay = “twenty-first” }
22 {$properDay = “twenty-second” }
23 {$properDay = “twenty-third” }
24 {$properDay = “twenty-fourth” }
25 {$properDay = “twenty-fifth” }
26 {$properDay = “twenty-sixth” }
27 {$properDay = “twenty-seventh” }
28 {$properDay = “twenty-eight” }
29 {$properDay = “twenty-ninth” }
30 {$properDay = “thirtieth” }
31 {$properDay = “thirty-first” }

}
$Voice.Speak( “Today is $day the $properDay”, 1 )

The voice utilized by this com object reminds me of the voice from the movie
“War Games”. The classic line is “Would you like to play a game?”

Get or Add Local Group Members to a Remote Computer

Here are a couple of interesting Powershell scripts that can be used to automate the addition of network accounts from one or more AD domains into the local Administrators group on a networked server or computer.

Script 1 will be used to add members of any trusted domain to the local Administrators group on a list of computers. In this example, I am going to add domain groups to the local Administrators group. Script 2 will be used to check group membership of the local Administrators group. The output of this script is exported to a spreadsheet to make review of the results easier.

Here is the scenario. Your manager emails you and says, ‘Hey Patrick old chum, please add these domain accounts from these domains to the local administrative groups on these servers. To make sure that I keep my job I am going to use ficticious names of domains and server.

Step 1: Create a text file called “computers.txt” in the same folder as the scripts. Each line of the text file will have the name or IP address of a networked computer or server on which we want to modify the local Administrators group. Now keep in mind, this process can be set to modify any local group on the list of computers, but I’ve chose the Administrators group for the sake of this discussion.



Step 2: Adding the desired accounts to the Administrators groups on remote computers.

Here is the script that will be used to add the members to the local groups.
add_to_admingroups.ps1 to add
****************************************************************
#add_to_admingroup.ps1
#patrick parkison
#email: patrickparkison@bellsouth.net
#This script uses powershell to add domain accounts (user or groups) to the local administrators
#group on remote computers.
#
#Reference for working with local groups on remote servers.
#http://powershellcommunity.org/Forums/tabid/54/view/topic/postid/1528/Default.aspx

#Get the list of computers to manange.
#Iterate through the list of computers.
foreach($i in (gc .\computers.txt)){

#Write to screen for feedback.
Write-Host “Processing “$i

#Add first user/group to remote Administrators group.
$objUser = [ADSI](“WinNT://DomainA/GroupA”)
$objGroup = [ADSI](“WinNT://$i/Administrators”)
$objGroup.PSBase.Invoke(“Add”,$objUser.PSBase.Path)

#Add second user/group to remote Administrators group.
$objUser = [ADSI](“WinNT://DomainB/GroupB”)
$objGroup = [ADSI](“WinNT://$i/Administrators”)
$objGroup.PSBase.Invoke(“Add”,$objUser.PSBase.Path)

#Add third user/group to remote Administrators group.
$objUser = [ADSI](“WinNT://DomainC/GroupC”)
$objGroup = [ADSI](“WinNT://$i/Administrators”)
$objGroup.PSBase.Invoke(“Add”,$objUser.PSBase.Path)

#Add fourth user/group to remote Administrators group.
$objUser = [ADSI](“WinNT://DomainD/GroupC”)
$objGroup = [ADSI](“WinNT://$i/Administrators”)
$objGroup.PSBase.Invoke(“Add”,$objUser.PSBase.Path)

#Add more accounts as required.

}

This is pretty is a pretty simple script. There are only two key points to look at.
The iteration of the remote computers from the computers.txt file occurs here:

foreach($i in (gc .\computers.txt)){

$i becomes that value of each computer name in the text file.
The second key point is actuall connection and manipulation of the local groups. That is done here:
#Add first user/group to remote Administrators group. $objUser = [ADSI](“WinNT://DomainA/GroupA”) $objGroup = [ADSI](“WinNT://$i/Administrators”) $objGroup.PSBase.Invoke(“Add”,$objUser.PSBase.Path)

Notice that $i will contain the name of each remote computer. Also, Administrators could be replaced by any valid group name.

Here is how the output of the script looks when it runs.

$ Add_to_admingroup.ps1
Processing s30004w014011
Processing 10.87.52.198
$

You would get two possible errors with this script.

The first would be if the group or user account was already a member of the local group that you are updating. That error looks like this:
Exception calling “Invoke” with “2” argument(s): “Exception has been thrown by the target of an invocation.”At I:\Utilities\PowerShellScripts\Local-Groups\add_to_admingroup.ps1:53 char:25+ $objGroup.PSBase.Invoke( <<<< “Add”,$objUser.PSBase.Path)

The second error would be if the remote computer were not found on the network.

That takes care of the first script. Now here is a good method to check the membership of a specific group on a list of remote computers. As indicated above, the output is displayed in a spreadsheet.

The second script is called list_admin_group_members.ps1. Here is the text of the script.

list_admin_group_members.ps1
****************************************************************
#Assign account names to variables.
$group1 = “GroupName1”
$group2 = “GroupName2”
$group3 = “GroupName3”
$group4 = “GroupName4”

#Open a spreadsheet
#Region
$RowCount = 1
#http://www.microsoft.com/technet/scriptcenter/resources/qanda/sept06/hey0908.mspx
$a = New-Object -comobject Excel.Application
$b = $a.Workbooks.Add()
$c = $b.Worksheets.Item(1)
$c.Cells.Item($RowCount,1) = “Server”
$c.Cells.Item($RowCount,2) = $group1
$c.Cells.Item($RowCount,3) = $group2
$c.Cells.Item($RowCount,4) = $group3
$c.Cells.Item($RowCount,5) = $group4
$a.Range(“A1:E1”).Select()
$a.Selection.Font.Bold = $True
$a.Columns.AutoFit()
$a.Visible = $True

#EndRegion

foreach($i in (gc .\computers.txt)){
Write-Host “Processing server $i.”
$script:RowCount += 1 #Increment row count.
$group =[ADSI]”WinNT://$i/Administrators”
$members = @($group.psbase.Invoke(“Members”))
$adminGrp = $members foreach {$_.GetType().InvokeMember(“Name”, ‘GetProperty’, $null, $_, $null)}
$c.Cells.Item($RowCount,1) = $i
$c.Cells.Item($RowCount,2) = ($adminGrp -contains $group1)
$c.Cells.Item($RowCount,3) = ($adminGrp -contains $group2)
$c.Cells.Item($RowCount,4) = ($adminGrp -contains $group3)
$c.Cells.Item($RowCount,5) = ($adminGrp -contains $group4)
}
$a.Range(“B2”).Select()
$a.ActiveWindow.FreezePanes = $True
$a.Columns.AutoFit()

Here is the part of that section that you’ll want to modify:
$group1 = “GroupName1”
$group2 = “GroupName2”
$group3 = “GroupName3”
$group4 = “GroupName4”

This assigns that the actual text that you are looking for. You would change this to a real group name that exist in the domain(s) that you are searching.

There are two main sections to this script. The first section is used to setup the spreadsheet. This is pretty useful by itself. I’ve included the reference where I learned how to configure the spreadsheet. If you do much reporting you’ll find that to be a pretty useful link.

$c.Cells.Item($RowCount,1) = “Server”
$c.Cells.Item($RowCount,2) = $group1
$c.Cells.Item($RowCount,3) = $group2
$c.Cells.Item($RowCount,4) = $group3
$c.Cells.Item($RowCount,5) = $group4

This sets up the first row of the spreadsheet, or the column header. You could added or remove the group names as required. Just add any addition groups in subsequent columns, i.e. $RowCount,X

The next three lines are used to manipulate the bold and width features of the spreadsheet. They simply make the spreadsheet more readable.

$a.Range(“A1:E1”).Select()
$a.Selection.Font.Bold = $True
$a.Columns.AutoFit()

The next section will iterate iterate through the text file computers.txt, and search the Administrators group on each computer.


foreach($i in (gc .\computers.txt)){

If you wanted to check the membership on a different group you would change that here.

$group =[ADSI]”WinNT://$i/Administrators”

This piece of code does the actual work of searching the remote computer for the group membership.

$group =[ADSI]”WinNT://$i/Administrators”
$members = @($group.psbase.Invoke(“Members”))
$adminGrp = $members foreach {$_.GetType().InvokeMember(“Name”, ‘GetProperty’, $null, $_, $null)}

And for the output to the spreadsheet, for each cell the name of each domain account is checked against the value of $adminGrp.

If the value of $groupX is found in the contents of $adminGrp, then a True is placed into the current cell, other wise a False is placed into the current cell.

$c.Cells.Item($RowCount,2) = ($adminGrp -contains $group1)

Finally some final manipulation of the spreadsheet is done for neatness.

$a.Range(“B2”).Select()
$a.ActiveWindow.FreezePanes = $True
$a.Columns.AutoFit()

Here is how the output looks on the screen looks when the script is run:

$ .\list_admin_group_members.ps1
True
True
Processing server s30004w014011.
Processing server 10.87.52.198.
True
True
$

Also here is a screenshot of how the spreadsheet looks once the script has run:

That’s it for this script. Please let me know if you have any questions, or issues when running this script.