Skip to content

Exchange Server 2007 monitoring with PowerShell

21-Mar-07

On Stefan Stranger’s blog, there is a link to a post on the PowerGadget’s forum about some interesting PowerShell scripts that output to PowerGadgets. The end result is something like:

Windows Installer PowerShell Extensions

16-Mar-07

Heath Stewart released a PowerShell extension that works with Windows Installer. For now, two cmdlets are provided:

  1. Get-MSIPatchInfo
  2. Get-MSIProductInfo

Check out CodePlex for full details and download.

Uploading Documents To SharePoint

13-Mar-07

Uploading documents to SharePoint is a common requirement in any SharePoint project. There are many tools at your disposal to accomplish that task but you can also leverage PowerShell in combination with the .NET FrameWork and the SharePoint object model. The script below is an example of how to upload files to SharePoint (save it as a PowerShell script with a ps1 extension):

begin
{
 $propbag=@{”ContentType”=”Document”
                       “MyCol” =”PowerShell About Docs” }
 $docliburl=”http://moss.newtech.local/Docs/Documents”
 $relweburl=”/Docs”
 [System.Reflection.Assembly]::LoadWithPartialName(”Microsoft.SharePoint”) > $null
 $site=new-object Microsoft.SharePoint.SPSite($docliburl)
 $web=$site.openweb($relweburl)
 $folder=$web.getfolder($docliburl)
 $list=$web.lists[$folder.ContainingDocumentLibrary]
}

process
{

 ## I expect FileInfo objects in the pipeline
 $bytes=get-content $_ -encoding byte
 $bytes=[byte[]]$bytes
 $folder.files.Add($_.Name,$bytes,$propbag, $true) > $null
}

The $propbag variable contains a hashtable with keys and values. It is used to set document properties. In this case, we set the ContentType (using the special key ContentType) and a custom column called MyCol.

Because we will use the classes in Microsoft.SharePoint.dll, that assembly needs to be loaded first (LoadWithPartialName). Then we get the SPSite in $site (http://moss.newtech.local), the SPWeb in $web (/docs, relative to the SPSite), the folder (Documents) in $folder and the SPList in $list.

The process{} script block does the actual uploading and it expects file objects in the pipeline. First, we get the content of each file in byte encoding and convert that to a byte array. To add files to the library, you need to specify 4 parameters to $folder.files.add:

  1. The name for the file in the document library. $_.name is used to use the same name as the original file.
  2. The contents of the file as a byte array.
  3. The hashtable with the properties that need to be set.
  4. $true to overwrite and $false if not.

How to use the script? It is pretty easy actually. Do something like this:

gci C:\WINDOWS\system32\windowspowershell\v1.0\*.txt | ./upload-file.ps1

The above example gets all the txt files from the PowerShell installation directory and uploads them to SharePoint according to the settings in the script. In SharePoint, you will see something like:

Note that you need to run the script on a server with SharePoint installed (WSS or MOSS) and that server needs to have PowerShell as well. Also note that this script is just an example of what is possible with some basic functionality. But it should be easy to extend according to your needs. Have fun!

Resources

08-Mar-07

This page is a work-in-progress.

Learning

Administration/Deployment

Active Directory

User Interface

Files, Folders

Exchange

SharePoint

XML

Windows Mobile

Release: PowerShell Community Extensions 1.1

08-Mar-07

PowerShell Community Extensions 1.1 have been released. Just go to CodePlex and download the new version. To upgrade to the new version, first uninstall the old version. The new version also installs perfectly on Windows Vista.

There are many new cmdlets and features in this version. To find out more, just type man about_pscx. The first thing you will notice is that the man alias actually is mapped to a Get-PagedHelp script (a ps1 file). That one actually lets you view help using the less function which provides an experience similar to the less utility in Unix/Linux. It is better than the default paging because you can use HOME, END, PGUP, PGDN and many other things (just type H while you are using less).

Some of the new features:

  • Set the variable $PscxFileSizeInUnitsPreference to $true (in your profile) to see file sizes in B,KB,MB,GB. Like the human readable flag in some Unix/Linux commands.
  • Get-ADObject cmdlet: search for objects in AD or the global catalog and get some of their properties.
  • Get-DomainController: get the domain controllers for a domain
  • Write-Zip: cool cmdlet to create zip archives. Very powerful when combined with the filtering capabilities of PowerShell. Also check out Write-GZip, Write-BZip2, Write-Tar.
  • Get-TerminalSession and Stop-TerminalSession
  • Get-Privilege and Set-Privilege: get and set privileges like SeShutDownPrivilege etc…
  • Resolve-Host and Ping-Host to resolve and ping hosts
  • Get-Random to get a random number
  • Get-FileVersionInfo: use it like “dir c:\windows\*.exe” | gfvi” to see the product version and file version of those files.
  • Add-PathVariable
  • Get-DiskUsage

They have also added a DirectoryServices provider to navigate Active Directory as a PSDrive. If you use the command GET-PSPROVIDER, you will see it in the list with your domain as a drive. You can then just use CD <YOURDOMAIN>:. See an example below of a DIR command at the root of my test domain:

Lots of interesting stuff in this release so check it out!

Working with paths in PowerShell

06-Mar-07

An administrator often has to work with paths. Luckily, PowerShell has a couple of cmdlets that make it easy to work with paths, test their validity or extract just the information you need. The test-path cmdlet checks if the path refers to a valid item. For example:

test-path c:\config.sys

If you want to check for the existence of a file or directory, use the -PathType parameter. As PathType specify Any, Container or Leaf. For example:

test-path c:\config.sys -PathType container

returns False because config.sys is a file (leaf) and not a container.

Test-Path can also be used to check if a path is valid without checking for the existence of the actual item. Use the -IsValid switch for that. For example:

test-path c:\config.sys -IsValid

will return True even in c:\config.sys does not exist. It returns True because the path is constructed in a valid way.

The split-path cmdlet provides some functionality to extract parts of a path. For example, take the following path:

c:\windows\system32\WindowsPowerShell\V1.0\powershell.exe

Now let’s take a look what split-path can do with it.

  • “split-path c:\windows\system32\WindowsPowerShell\V1.0\powershell.exe” returns c:\windows\system32\WindowsPowerShell\V1.0. Without any additional parameters, split-path just returns the parent. It is the same as using the -parent switch.
  • “split-path -leaf c:\windows\system32\WindowsPowerShell\V1.0\powershell.exe” returns powershell.exe.
  • “split-path -qualifier c:\windows\system32\WindowsPowerShell\V1.0\powershell.exe” returns c:. It returns the drive of the path. This could also be HKCU: or HKLM:.

split-path also has a -resolve parameter. This can be handy if you use wildcards in the path. For example, the following command

split-path -leaf -resolve C:\Windows\system32\WindowsPowerShell\v1.0\*.ps1xml

returns a collection of strings of all the file names ending with .ps1xml. Note the difference with “dir *.ps1xml” which returns a collection of FileInfo objects.

The .NET class System.IO.Path contains even more useful methods to work with paths. To call methods of the Path class from PowerShell, put the full name of the class in square brackets followed by :: and the method name. For example, to call the GetExtension method of the Path class, do this:

[System.IO.Path]::GetExtension(”c:\config.sys”)

The above would return ”.sys”. To find out what other methods you can use, type the following:

[System.IO.Path] | gm -static

The above command uses the get-member cmdlet (gm) with the -static switch to get the static methods of the Path class. The static method GetTempFileName can be useful if you need a temporary file in the temp folder ($env:temp). The method creates a 0-byte file in the temp folder and returns the full path to the file as a string. For example:

$tempfile=[System.IO.Path]::GetTempFileName()
dir > $tempfile
invoke-item $tempfile

Another method, GetRandomFileName(), just returns a random file name as a string. It does not use the temp folder and does not create the file either.

There are some other cmdlets that work with paths like JOIN-PATH, CONVERT-PATH and RESOLVE-PATH. You can use the man alias with -full switch to get help about those. For example:

man join-path -full

If you use the PowerShell Community Extensions, you have some other commands at your disposal:

  • GET-SHORTPATH: gets the 8.3 name for a given path (e.g. get-shortpath “c:\program files”)
  • ADD-SHORTPATH: this is a filter (alias=asp) that adds the short path to the output of dir. Use it like this: “dir | asp | ft name,shortpath,length -auto”.

Getting VMFS datastore information with PowerShell

01-Mar-07

Well, actually, I should add “and a bit of Perl” to the title. But first, let me explain the idea behind the script. It is a simple script for administrators of VMware Infrastructure to do some reporting on the VMFS datastores in a datacenter. It does some things like calculating the total percentage of free space but that is not the point. The point is: “how do we get to the datastore information and what can we do with it in PowerShell”.

To work with VMware Infrastructure from scripts and custom programs, VMware provides a web service running on either VirtualCenter (a management server) or individual ESX boxes. Theoretically, you can connect to the web service from PowerShell using all available techniques that PowerShell provides. The problem is that the VMware web service is so complex that I do not want to spend much time with it (hey, I am not a developer). Luckily, there is the VI Perl Toolkit and instructions on how to install and use it on Windows. The PowerShell script uses the output of a Perl script that use the VI Perl Toolkit to get the datastore information. The Perl script (getds.pl) looks like this:

use strict;
use warnings;
use VMware::VIRuntime;

Vim::login(service_url => “https://$ARGV[0]/sdk/vimService”, user_name => “$ARGV[1]”, password => “$ARGV[2]”);
my $dc = Vim::find_entity_view(view_type => “Datacenter”, filter => {’name’ => $ARGV[3]} );
my $ds = $dc->datastore;

print “Name,Type,Capacity,FreeSpace\n”

foreach(@$ds) {
 my $ds_ref=Vim::get_view(mo_ref => $_);
 if($ds_ref->summary->multipleHostAccess eq “true”) {
  print “”" . $ds_ref->info->name . “”,”
  print $ds_ref->summary->type . “,”
  print $ds_ref->summary->capacity / (1024*1024) . “,”
  print $ds_ref->info->freeSpace / (1024*1024) . “\n”
}}

The Perl script needs to be run like this:

perl getds.pl <vcsserver> <username> <password> <datacenter_name>

The output of the script can be saved as csv for further processing. The output would look like this:

Name,Type,Capacity,FreeSpace
“DS1″,VMFS,255744,165082
“DS2″,VMFS,255744,236510
“DS3″,VMFS,255744,211395

What I would like to do is get the output from the Perl script and turn it into something usable in PowerShell. That is not too difficult with the following PowerShell script (get-ds.ps1 without almost any error handling, just enough for a lazy admin):

# check if there are 2 parameters
if ($args.Count -ne 2) {
 Write-Error “You need to specify two parameters: vcserver and datacenter”
 Exit
}

$path_to_perl_script=”c:\getds.pl”
$path_to_csv=”c:\datastores.csv”
$vc_server=$args[0]
$datacenter=$args[1]

# use get-credential to get the user account (domain\user) and password (secure string)
$cred=(get-credential)

# this function is used to convert the securestring into cleartext for passing it to the perl script
function Convert-SecureString($sstring) {
 $BSTR = [System.Runtime.InteropServices.marshal]::SecureStringToBSTR($sstring)
 $ClearString=[System.Runtime.InteropServices.marshal]::PtrToStringAuto($BSTR)
 [System.Runtime.InteropServices.marshal]::ZeroFreeBSTR($BSTR)
 $ClearString
}

# run the perl script and export the text results to a csv file ($path_to_csv gets overwritten)
perl $path_to_perl_script $vc_server $cred.username (convert-securestring($cred.password)) $datacenter > $path_to_csv

# import from the csv file into a PSCustomObject, simpler than creating your own custom object
$dsps=import-csv $path_to_csv

# calculate the total capacity and total free space with measure-object
$total_capacity=($dsps | measure-object -Property capacity -sum).sum / 1024
$total_free=($dsps | measure-object -Property freespace -sum).sum / 1024

# write out some information and format
write-host “`nCapacity Info`n=============” -foregroundcolor yellow
write-host (”Total capacity: {0:N0} GB” -f $total_capacity)
write-host (”Total free: {0:N0} GB” -f $total_free)
write-host (”Percentage free: {0:P0}`n” -f ($total_free / $total_capacity))

# return $dsps so the admin can do some further processing with it
return $dsps

The get-ds.ps1 script takes two parameters: the name of the VirtualCenter server and the name of a datacenter object in VirtualCenter. For example:

./get-ds.ps1 vcserver01 Brussels

The output would be (datastore names removed):

The admin can also call the PowerShell script as follows:

$ds=./get-ds.ps1 vcserver01 Brussels

In that case, he would see the calculations but not the table with an overview of datastores. The data for that table is stored in the $ds variable (PSCustomObject) and can be used to perform further analysis. For instance:

$ds | ? { ([int]$_.FreeSpace / [int]$_.Capacity) -lt 0.4}

The above would only return the datastores with less than 40% free space.

The next step is to bypass the Perl script completely and do it all in PowerShell but then I need to explore the VMware SDK in more detail.

PowerShell and web services without the hassle

28-Feb-07

I wrote about using the SharePoint web services from PowerShell before. In order to consume the web service, you needed wsdl.exe to generate a proxy class and csc.exe to generate an assembly. As Lee Holmes points out, those requirements make such PowerShell scripts not very portable because wsdl.exe is not part of the .NET Framework. Check out his blog post that describes how to consume a web service without those tools. Pretty ingenious to say the least!

If you want to use that function with SharePoint web services, you will need to provide some authentication. A quick solution is to add the following line:

$wc.Credentials=[System.Net.CredentialCache]::DefaultCredentials

behind the line that says:

$wc = new-object System.Net.WebClient

Of course, you can modify the function to provide authentication only if needed.

PowerShell Remoting

28-Feb-07

If you want to run PowerShell on remote machines, you can try PowerShell Remoting. It remotes the user interface with a couple of restrictions. For example, you cannot use command completion (<TAB>) and you cannot execute ‘external’ programs (.exe). Installation is simple: install a server component and a client component and you are done. With a user.xml file, you can restrict who connects.

A remote session is started with a GUI as shown below:

After connecting you get a command prompt on the remote machine.

Of course, you can also install something like winSSHD (not free) from Bitvise and use Bitvise Tunnelier (ssh client) to get a remote command prompt. The Bitvise software works well as there are no limitations as described here. So when I connect to winSSHD with Tunnelier and then execute powershell.exe I get a prompt and can start to work with it. Command completion works as expected.

PowerShell and registry access

28-Feb-07

PowerShell can access the registry like any PowerShell drive. Two drives are available: HKCU (HKEY_CURRENT_USER) and HKLM (HKEY_LOCAL_MACHINE). Navigate to HKLM or HKCU like so:

cd hklm:
cd hkcu:

Isn’t that simple? Now you can use the dir or ls command (aliases for get-childitem) to list the content. The result:

I usually create another PS drive that maps to HKLM\System\CurrentControlSet\Services with the command (in the profile):

New-PSDrive -Name regsvc -root “hklm:\system\currentcontrolset\services” -PSProvider registry

The get-childitem cmdlet only returns subkeys and not the properties of a key (see this page for more information). To get at the properties of a key, use the get-childitemproperty cmdlet. For example:

cd hkcu:\Keyboard Layout\Preload
get-itemproperty

To create a key in the registry, use the mkdir command as you would with a folder on the file system. For example:

mkdir hkcu:\myregkey

The above command creates the key myregkey is HKEY_CURRENT_USER. To remove it use rmdir or remove-item.

If you want to create an extra property, use the new-itemproperty cmdlet. That cmdlet takes a few parameters like the name of the property (-name), the type of the property (-propertytype) and the value (-value). As property type you can use: string, expandstring, binary, dword and multistring. An example:

New-ItemProperty HKCU:\myregkey -name myprop -propertytype DWord -value 1

This is all fine for local registry access but what if you want to work with remote registries? Well, sadly, that is not possible with native PowerShell commands. Because PowerShell can use any .NET class, you could use the OpenRemoteBaseKey method of the RegistryKey .NET class but I don’t think it is always worth the hassle. To work with remote registries, you can use the REG.EXE application. To see if the file is available to you, use get-command reg.exe | fl. If available, you will get information about the location of the tool, the version and so on. On Vista, you will see that it is part of the operating system. For example, to query for a value on a remote system (one line):

reg query \geba-vista\hklm\system\currentcontrolset\services\lanmanserver\parameters /v nullsessionpipes

After an external command like above, use $lastexitcode to check for errors. In this case, if $lastexitcode returns 1, an error happened. Naturally, if you use reg.exe for remote registry access you just get text back and not objects. You will have to do some text parsing to get to the data you are interested in.

For those who just cannot resist using .NET, to do the same thing as the reg query command above:

$key=”system\currentcontrolset\services\lanmanserver\parameters”
$keytype=[Microsoft.Win32.RegistryHive]::LocalMachine
$server=”servername”
$remotebase=[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype,$server)
$regkey=$remotebase.OpenSubKey($key)
$regkey.GetValue(”nullsessionpipes”)

The last command returns the following:

netlogon
lsarpc
samr
browser

Of course, that is much easier to work with than the text returned by reg.exe. It’s just a collection of strings.