Thursday, January 31, 2008

PowerShell, WMI and SQL Server

I was recently asked "how can we determine the specific SKU of an installed SQL Server - without being able to execute T-SQL?". My answer:

The following one-liner lists various advanced properties of a SQL installation:

Get-WmiObject sqlserviceadvancedproperty -namespace "root\Microsoft\SqlServer\ComputerManagement" -computername | Select-Object -Property PropertyName, PropertyNumValue, PropertyStrValue

Friday, January 25, 2008

Google Chart API & PowerShell

Have you heard of Google's "Chart API"?
No?
Well, the Google Chart API lets you dynamically generate charts. Using only a browser. But why use a browser when you can use PowerShell instead? :-)
Click here to learn more about Google Chart API.

I had been playing around with Google Chart API for some time, when I decided to create a small PowerShell script to obtain a pie chart - sort of a poor mans replacement for Microsoft Excel or PowerGadgets:

Lets start by writing a small function to download and display an image. This will be a very basic function, so the only input in the function will be an URL.

function DownloadAndShowImage ($url) {
    $localfilename = ".\chart.png"
    $webClient = new-object System.Net.WebClient
    $webClient.Headers.Add("user-agent", "PowerShell Badass Script v666")
    $Webclient.DownloadFile($url, $localfilename)
    Invoke-Item $localfilename
}

Next, we need a function to create a valid Google Chart API url. Input parameters will be two arrays (data values and text), the desired size of the chart and an option to create a 3D pie chart insted of a plain 2D one:

function simpleEncoding ($valueArray, $labelArray, $size, [switch] $chart3D) {
    $simpleEncoding = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    if ($chart3D) {$chartType = "p3"} else {$chartType ="p"}
    $total = 0
    foreach ($value in $valueArray) {
        $total = $total + $value
    }
    for ($i = 0;$i -lt $valueArray.length;$i++) {
        $relativeValue = ($valueArray[$i] / $total)*62
        $relativeValue = [math]::round($relativeValue)
        $encodingValue = $simpleEncoding[$relativeValue]
        $chartData = $chartData + "" + $encodingValue
    }    
    $chartLabel = [string]::join("|",$labelArray)
    Write-Output "http://chart.apis.google.com/chart?cht=$chartType&chd=s:$chartdata&chs=$size&chl=$chartLabel"
}

That's it! We are now ready to create a small Chart. Let's try it out:

$values = 100,11,40,9
$text = "Hans","Jane","Rose","Simon"
$url = simpleEncoding $values $text "320x150" -Chart3D
DownloadAndShowImage $url

The above should result in your local .png viewer showing something like this:

How about something a little more advanced?
Lets try it - but first we need a small support function:

function GetProcessArray() {
    $ListOfProcs = Get-Process | Sort-Object CPU -desc | Select-Object CPU, ProcessName -First 15
    for ($i = 0;$i -lt $ListOfProcs.length ;$i++) {
        $ProcName = $ProcName + "," + $ListOfProcs[$i].ProcessName
        $ProcUsage = $ProcUsage + "," + $ListOfProcs[$i].CPU
    }
    Write-Output (($ProcName.trimStart(",")).split(","), ($ProcUsage.trimStart(",")).split(","))
}

Now we are ready to create a more detailed graph, this time of the top 15 processes counted by CPU seconds consumed.

$data = GetProcessArray
$url = simpleEncoding $data[1] $data[0] "700x350"
DownloadAndShowImage $url

The result should look like this:

Thursday, January 24, 2008

DBCC check through ADO.NET/PS

Here is a small script I use to run DBCC against various databases in my environment:

$ScriptName = $myInvocation.MyCommand.Name
[void][reflection.assembly]::LoadWithPartialName("System.Data.SqlClient")
$ConnString = "Server=Servername\Instance;Integrated Security=SSPI;Database=DatabaseName;Application Name=$ScriptName"
$MasterConn = new-object ('System.Data.SqlClient.SqlConnection') $ConnString
$MasterCmd = new-object System.Data.SqlClient.SqlCommand
$MasterCmd.Connection = $MasterConn
$SqlDBCC = "DBCC CHECKDB(master) WITH TABLERESULTS"
$MasterCmd.CommandText = $SqlDBCC
$MasterConn.Open()
$Rset = $MasterCmd.ExecuteReader()
If ($Rset.HasRows -eq $true) {
    While ($Rset.Read()) {
        $line = $Rset["MessageText"]
        If ($Rset["Level"] -gt 10) {
            Write-Host $line -backgroundcolor Yellow -foregroundcolor Red
        } else {
            Write-Host $line
        }
    }
    $Rset.Close()
}
$MasterConn.Close()

Friday, December 21, 2007

Send-SMTPmail update

Version 1.2 of my Send-SMTPmail cmdlet:

# PowerShell 1.0 script to send email through SMTP
# by Jakob Bindslet (jakob@bindslet.dk), 21. december 2007
# Current Script version 1.1
#
#NAME
#    Send-SMTPmail
#
#SYNOPSIS
#    Sends an email by use of an SMTP server
#
#SYNTAX
#    Send-SMTPmail -to -from -subject -body
#    [-smtpserver ] [-port ] [-attachment ] [-html]
#    [-cc ] [-bcc ] [-alert] [-timeout ]
#
#PARAMETERS
#    No specific order of the parameters are required:
#        -to
#        -from
#        -subject
#        -body
#    Optional arguments are:
#    -smtpserver
#        Address of the smtp server to be used. This is only optional if a
#        default    smtpserver has been specified
#    -port
#        port number of the smtp server, used only if it differs from the
#        standard port 25.
#    -attachment
#        Complete path to file to attach, ie. "c:\boot.ini"
#    -cc
#        Recipient(s) to be included as "carbon copy" on the email.
#    -bcc
#        Recipient(s) to be included as "blind carbon copy" on the email.
#    -timeout
#        Sets the timeout in miliseconds, for the send mail operation.
#        This includes the time it take to upload any attachments, so
#        be careful of setting this too low if using attached file.
#    -alert
#        The email will be treatet as an alert (the standard sharepoint
#        alert icon will be used in outlook).
#    -html
#        Indicates that the email will be in HTML format. The HTML
#        content itself should be specified in the -body parameter.
#
#NOTES
#
# -------------------------- EXAMPLE 1 --------------------------
# C:\PS>send-SMTPmail -to hans@contoso.msft -from theBoss@contoso.msft
#    -subject "Congratulations!" -smtpserver "smtp.contoso.msft"
#    -body "you'll get one extra week of vacation year"
#    -bcc "theMiddleBoss@contoso.msft"

function Send-SMTPmail($to, $from, $subject, $body, $attachment, $cc, $bcc, $port, $timeout, $smtpserver, [switch] $html, [switch] $alert) {
    if ($smtpserver -eq $null) {$smtpserver = "smtp.myserver.com"}
    $mailer = new-object Net.Mail.SMTPclient($smtpserver)
    if ($port -ne $null) {$mailer.port = $port}
    if ($timeout -ne $null) {$mailer.timeout = $timeout}
    $msg = new-object Net.Mail.MailMessage($from,$to,$subject,$body)
    if ($html) {$msg.IsBodyHTML = $true}
    if ($cc -ne $null) {$msg.cc.add($cc)}
    if ($bcc -ne $null) {$msg.bcc.add($bcc)}
    if ($alert) {$msg.Headers.Add("message-id", "<3bd50098e401463aa228377848493927-1>")}
    if ($attachment -ne $null) {
        $attachment = new-object Net.Mail.Attachment($attachment)
        $msg.attachments.add($attachment)
    }
    $mailer.send($msg)
}

Thursday, December 20, 2007

A cmdlet-like email script

I'm currently working on a database monitoring, datacollection and automation project using PowerShell. In this project I often find m self in need of sending a status or warning email when some event occurs - For a while I've just used code much like what I posted here on my blog back in december 2006, but last night I spend a bit of time writing up a cmdlet like "Send-SMTPmail" function once and for all.
Please feel free to comment the script, its functionality and implementation.

# PowerShell 1.0 script to send email through SMTP
# by Jakob Bindslet (jakob@bindslet.dk), 20. december 2007
# Current Script version 1.0
#
#NAME
# Send-SMTPmail
#
#SYNOPSIS
# Sends an email by use of an SMTP server
#
#SYNTAX
# Send-SMTPmail -to -from -subject -body
# [-smtpserver ] [-port ] [-attachment ] [-html "yes"]
# [-cc ] [-bcc ] [-alert "yes"] [-timeout ]
#
#PARAMETERS
# No specific order of the parameters are required:
# -to
# -from
# -subject
# -body
# Optional arguments are:
# -smtpserver
# Address of the smtp server to be used. This is only optional if a
# default smtpserver has been specified
# -port
# port number of the smtp server, used only if it differs from the
# standard port 25.
# -attachment
# Complete path to file to attach, ie. "c:\boot.ini"
# -html "yes"
# Indicates that the email will be in HTML format. The HTML
# content itself should be specified in the -body parameter.
# -cc
# Recipient(s) to be included as "carbon copy" on the email.
# -bcc
# Recipient(s) to be included as "blind carbon copy" on the email.
# -alert "yes"
# The email will be treatet as an alert (the standard sharepoint
# alert icon will be used in outlook).
# -timeout
# Sets the timeout in miliseconds, for the send mail operation.
# This includes the time it take to upload any attachments, so
# be careful of setting this too low if using attached file.
#
#NOTES
#
# -------------------------- EXAMPLE 1 --------------------------
# C:\PS>send-SMTPmail -to hans@contoso.msft -from theBoss@contoso.msft
# -subject "Congratulations!" -smtpserver "smtp.contoso.msft"
# -body "You'll get one extra week of vacation year"
# -bcc "theMiddleBoss@contoso.msft"

function Send-SMTPmail() {
## Setting defaults
$to = $null; $from = $null; $from = $null; $subject = $null; $body = $null;
$attachment = $null; $html = $null; $cc = $null; $bcc = $null; $port = $null
$alert = $null; $timeout = $null

$smtpserver = "smtp.myserver.com"
$LastArg = $args.length -1
for ($i = 0; $i -le $LastArg; $i += 2) {
if ($args[$i].substring(0,1) -eq "-") {
Write-Host "param" $args[$i] "value" $args[$i+1] ##debug line, can be removed
switch ($args[$i]) {
-smtpserver {$smtpserver = $args[$i+1]}
-to {$to = $args[$i+1]}
-from {$from = $args[$i+1]}
-subject {$subject = $args[$i+1]}
-body {$body = $args[$i+1]}
-attachment {$attachment = $args[$i+1]}
-html {$html = "yes"}
-cc {$cc = $args[$i+1]}
-bcc {$bcc = $args[$i+1]}
-port {$port = $args[$i+1]}
-alert {$alert = "yes"}
-timeout {$timeout = $args[$i+1]}
}
}
}
$mailer = new-object Net.Mail.SMTPclient($smtpserver)
if ($port -ne $null) {$mailer.port = $port}
if ($timeout -ne $null) {$mailer.timeout = $timeout}
$msg = new-object Net.Mail.MailMessage($from,$to,$subject,$body)
if ($html -eq "yes") {$msg.IsBodyHTML = $true}
if ($cc -ne $null) {$msg.cc.add($cc)}
if ($bcc -ne $null) {$msg.bcc.add($bcc)}
if ($alert -eq "yes") {$msg.Headers.Add("message-id", "<3bd50098e401463aa228377848493927-1>")}
if ($attachment -ne $null) {
$attachment = new-object Net.Mail.Attachment($attachment)
$msg.attachments.add($attachment)
}
$mailer.send($msg)
}

Thursday, December 13, 2007

Script to extract disk space usage through WMI

This script can be used to extract information regarding sapce usage (actually free disk space in percent) from one or more servers or cluster. Since the script uses Win32_Volume, it can also recognize mountpoints.

$outData = @("")
$server = $args[0]
$dataFromServer = Get-WmiObject Win32_Volume -ComputerName $server | Select-Object SystemName,Label,Name,DriveLetter,DriveType,Capacity,Freespace

foreach ($currline in $dataFromServer) {
    if ((-not $currline.name.StartsWith("\\")) -and ($currline.Drivetype -ne 5)) {
        [float]$tempfloat = ($currline.Freespace / 1000000) / ($currline.Capacity / 1000000)
        $temppercent = [math]::round(($tempfloat * 100),2)
        add-member -InputObject $currline -MemberType NoteProperty -name FreePercent -value "$temppercent %"
        $outData = $outData + $currline
    }
}

$outData | Select-Object SystemName,Label,Name,Capacity,FreePercent | sort-object -property FreePercent | format-table -autosize

Friday, November 16, 2007

PowerShell support in SQL Server 2008

Latest news from IT-Forum/TechEd 2007 Barcelona, is that the upcomming CTP 5 release of MS SQL Server 2008 won't come with integrated PowerShell - this feature won't be available until CTP 6 of SQL Server 2008 :-(

Tuesday, November 06, 2007

Powershell V2 Approaching

If you haven't noticed already, Jeffrey Snover has announced the immediate release of PowerShell V2 CTP.

The PowerShell V2 CTP is not for everyone. You should read this PowerShell Team blog entry ( http://blogs.msdn.com/powershell/archive/2007/11/02/ctp-watch-this-space.aspx ) to find out what it is and what it isn't and then make an informed decision before installing the CTP.

Wednesday, October 10, 2007

ClusterSize

I recently had to complete a survey of the actual clustersizes used on a bunch of SQL Servers. In order to obtain this information, I came up with the small script below.
The script will list all drives (including drives without driveletters assigned, only mountpoints):

As noted by a reader the WMI class "Win32_Volume", is only available on Windows 2003 Server & Windows 2008 Server.

$servers = "myServer1.domain.x", "myServer2.domain.y"
$accData = $null
foreach ($server in $servers) {
    $dataFromServer = Get-WmiObject Win32_Volume -ComputerName $server | Select-Object SystemName,Name,DriveLetter,DriveType,Blocksize,Filesystem
    $accData = $accData + $dataFromServer
}

$accData | ConvertTo-Html | Set-Content "VolumeList.html"

Thursday, August 30, 2007

Formatting through the -f parameter

Formatting in PowerShell is typically achieved through the use of .NET's format strings. You might have seen cryptic statements like:

"{0,-8:P1}" -f 1.75

The above format can be explained as:

"{argument index[,alignment][:formatString zeros]}"

Note that both the alignment and the formatString are optional.

argument index is used when formatting more than one argument.

##### Argument Index
"{0,8}{1,10}" -f "John", "Doe"        # " John Doe"
"{0,8}{1,10}" -f "Albert", "Smith"    # " Albert Smith"

alignment indicates the minimal length of the string.
Positive valus are right justified and padded with spaces on the left if the string size is shorter than the value.

Negative values are left justified and padded with spaces on the right if the string size is shorter than the value.

##### Alignment

"{0,10}" -f "orange"     # " orange"
"{0,-10}" -f "orange"    # "orange "

format string is used mainly for numeric formatting.

##### C - Currency, the number represents the number of trailing zeroes
"{0:C3}" -f 25        # "$25.000"
"{0:C3}" -f -25        # "($25.000)"

##### D - Decimal, the number represents the number of leading zeroes
"{0:D5}" -f 25        # "00025"
"{0:D5}" -f -25        # "-00025"

##### E - Exponential, the number represents the number of trailing zeroes
"{0:E5}" -f 25        # "2.50000E+E001"
"{0:E5}" -f -25        # "-2.50000E+E001"

##### N - Number, the number represents the number of trailing zeroes
"{0:N5}" -f 25        # "25.00000"
"{0:N5}" -f -25        # "-25.00000"

##### P - Percentage, the number represents the number of leading zeroes
"{0:P3}" -f 25        # "2,500.000%"
"{0:P3}" -f -25        # "-2,500.000%"

##### X - Hexadecimal, the number represents the number of leading zeroes
"{0:X5}" -f 25        # "00019"
"{0:X5}" -f -25        # "FFFFFFE7"


Here are a few examples. Try them yourself.

"{0,9:P2}{1,20}" -f 0.09,"Standard loan"
"{0,9:P2}{1,20}" -f 0.25,"High interest loan"
"{0,9:P2}{1,20}" -f 3.5,"Mafia loan"

"{0,10:N3}{1,10:N3}{2,10:N3}{3,10:N3}" -f 0.2, 0.3, 0.45, 0.91
"{0,10:N3}{1,10:N3}{2,10:N3}{3,10:N3}" -f 1.1, 3, 2.5, 91.1
"{0,10:N3}{1,10:N3}{2,10:N3}{3,10:N3}" -f 25, 300, 211.5, 450

Wednesday, August 29, 2007

Is my drive fragmented?

Marco Shaw commented on my post ragarding fragmentation of files. Unfortunately WMI doesn't expose the fragmentation of specific files (at least I can't find it). But Marco's approach is useful in many situations.

Here is a small example on how to retrieve and display the fragmentation staus from a series of servers:

$allservers = "server1.", "server2"

Write-Host -foregroundcolor Yellow "Server         Drive Label         ClusterSize AverageFragmentsPerFile"
Write-Host -foregroundcolor Yellow "-------------- ----- ------------ ----------- -----------------------"

foreach ($server in $allservers) {
    $allvolumes = Get-Wmiobject -class Win32_Volume -Computername $server
    foreach ($volume in $allvolumes) {
        if ($volume.drivetype -eq 3) {
            $output = "{0,-15} {1,-6} {2,-15} {3,8} {4,15}" -f $server, $volume.DriveLetter, $volume.Label, $volume.DefragAnalysis().DefragAnalysis.ClusterSize, $volume.DefragAnalysis().DefragAnalysis.AverageFragmentsPerFile
            Write-Host -foregroundcolor White $output
        }
    }
}

Wednesday, August 22, 2007

A few nice variables

Obtaining the name of the current script: $myInvocation.MyCommand.Name
Path to Powershell executable: $PSHome
Get environment variables: $env:username or $env:logonserver
List all available environment variables: dir env: (note that no $ is included)
Show last commandline entered: $line
Current Path: $pwd

Thursday, August 09, 2007

Reading directly from SQL Server

A small example of reading a value from an SQL Server. In this case the maximum memory allowed for the SQL Server Instance.

$adOpenStatic = 3

$adLockOptimistic = 3
$timeout = 3

function GetMaxMemory {
    ## returns single string
    ## returns -1 if memory is unlimited
    ## returns -2 if no connection to the desired DB can be obtained
    ## arg0 = datasource (eg. server1\instance01)
    $objConnection = New-Object -comobject ADODB.Connection
    $objRecordset = New-Object -comobject ADODB.Recordset
    $query = "SELECT value FROM [master].[dbo].[sysconfigures] WHERE config = 1544"
    $objConnection.Set_ConnectionString("Provider=SQLOLEDB; Data Source=" + $args[0] + "; Initial Catalog=master; Integrated Security=SSPI")
    $objConnection.Set_ConnectionTimeout($timeout)
    $objConnection.Open()
    if ($objConnection.State -eq "1") {
        $objRecordset.Open($query, $objConnection, $adOpenStatic, $adLockOptimistic)
            $objRecordset.MoveFirst()
            $temp0 = ($objRecordset.Fields.Item("value").Value)
            if ($temp0 -eq 2147483647) {
                $temp0 = -1
            } else {
                $temp0 = $temp0 * 1mb
            }
        [long]$temp1 = $temp0
        Write-Output $temp1
        $objRecordset.Close()
        $objConnection.Close()
    } else {
        Write-output -2
    }
}

Usage:

GetMaxMemory servername\isntancename

Thursday, January 25, 2007

Is my file fragmented?

Small function to determine if a file is fragmented - for instance to analyzed an .ldf/.mdf file.
The function below also demonstrates one way to invoke an external command/tool (in this case "contig.exe" from www.sysinternals.com).
The function requires contig.exe to be in the local path.

function getFileFragments {
    $filename = $args[0]
    $contigOutput = contig -a $filename
    foreach ($line in $contigOutput) {
        if ($line -match "Average fragmentation") {
            $splitline = $line.split(' ')
            $x = $splitline.count
            Write-Host $splitline[$x -2]
        }
    }
}

Usage:

getFileFragments "c:\boot.ini"

Tuesday, January 02, 2007

Test connection to server

Small function to test connectivity to a server.

function test-connection {
    $pingtest = ping $args[0] -n 1
        if ($pingtest -match 'TTL=') {
        Write-Output $true
    } else {
        Write-host $false
    }
}

Usage:

test-connection MyServer01.domain.com

Wednesday, December 27, 2006

List current functions in powershell

You can use the following command to display what functions that are part of your current shell.

Get-ChildItem function:*

To view the code contained in a function - in this example the function "mkdir":

$function:mkdir

If you would like to change a function, simply create a new function of the same name:

function C: {
    Write-Host "You are not allowed on drive C: !"
    Set-Location M:
}

The function TabExpansion is called to generate the list of possible completion matches when using the TAB key.
The function prompt determines what your command prompt looks like (se earlier post) .

Thursday, December 21, 2006

Configuring PowerShell

Enable scripts - in order to execute script in PowerShell, the default Executionpolicy of "Restricted", has to be changed to "Unrestricted":

Set-Executionpolicy Unrestricted

Instead of "Unrestricted" you could also selec RemoteSigned, AllSigned, Restricted, Default (to reset to the PowerShell default)

Configure my profile
Determine where my profile is stored through the following default variable:

$profile

Edit it by using

notepad $profile

To create the file first:

new-item -path $profile -itemtype file -force

# Set Aliases
set-alias n notepad

# Set Prompt
function prompt
{
$nbsp;$nbsp;$nbsp;$nbsp;Write-Host ("--< " + $(Get-Location) + " >--< " + $(Get-Date) + " >--") -foregroundcolor DarkRed
$nbsp;$nbsp;$nbsp;$nbsp;Write-Host "PS>" -nonewline -foregroundcolor Cyan
$nbsp;$nbsp;$nbsp;$nbsp;return " "
}


  • %UserProfile%\My Documents\WindowsPowerShell\profile.ps1 - This profile applies only to the to the current user, but affects all shells.
  • %UserProfile%\\My Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 - This profile applies only to the current user and the Microsoft.PowerShell shell.
  • %windir%\system32\WindowsPowerShell\v1.0\profile.ps1 - This profile applies to all users and all shells.
  • %windir%\system32\WindowsPowerShell\v1.0\ Microsoft.PowerShell_profile.ps1 - This profile applies to all users, but only to the Microsoft.PowerShell shell.
Read more about it all here.

Wednesday, December 20, 2006

How much RAM have I got?

A small function to retrieve the number and size of RAM modules in a PC

function GetMem{
    $a = Get-WmiObject win32_physicalmemory -computername $args[0]
    Write-Host ("-" * 60)`n "Memory Configuration of" $args[0] `n("-" * 60)
    foreach ($b in $a) {
        Write-Host $b.tag `t $b.capacity "bytes" `t ($b.capacity/1mb) "mb"
    }
}

Usage:

GetMem

Useful .NET stuff

One of the big advantages of PowerShell is the easy access to .NET
For instance, in order to gain access to the .NET functionality contained in datatime we
can use Get-Member to look at the available methods:

$a = [datetime]
Get-Member -InputObject $a -s

Or written in a shorter notation:

[datetime] | gm -s

We might find the IsLeapYear or Parse methods interisting, and decide to try them out:

[datetime]::Parse("2008/02/29 012:23:45")

What? January 29th? Maybe 2008 is a leapyear? Try this:

[datetime]::IsLeapYear(2008)

Another interesting part of .NET might be math . Lets use it to round the number 9876.54321 to one decimal:

[math]::round(9876.54321,1)

Other interesting uses for .NET:

[datetime]::now.addminutes(-1000000) #give me datetime 1 million minutes ago)
[console]::title = "My PoweShell window" #sets the title of the current console window

Tuesday, December 19, 2006

Memory consumption

Here are two small script used for displaying the name of currently running processes, as well as the amount of memory currently allocated (through VMM) to Virtual Machines (either Microsoft Virtual PC or Virtual Server):

function GetMem {
    $processes = Get-WmiObject win32_process | Sort-Object -property WorkingSetSize -descending
    $processes | format-table `
         ProcessName, `
         Handle, `
         WorkingSetSize, `
         @{label="in MB"; Expression={([Math]::round($_.WorkingSetSize / 1mb,1))}} `
         -autosize
    $TotalMem= 0
    foreach ($process in $processes) {
         $TotalMem = $TotalMem + $process.WorkingSetSize
    }
    $TotalMem_in_MB = [math]::round($TotalMem/1mb,1)
    Write-Output "`tTotal mem used: `t $TotalMem ($TotalMem_in_MB MB)`n"
}

$a = GetMem; $a

function GetVMmem {
    $virtualmachines = Get-WmiObject virtualmachine -namespace "root\vm\virtualserver"
    $virtualmachines | format-table `
         @{label="ProcessName"; Expression={("VirtualMachine " + $_.Name)}}, `
         PhysicalMemoryAllocated, `
         @{label="in MB"; Expression={([Math]::round($_.PhysicalMemoryAllocated/1mb,1))}} `
         -autosize
    $TotalMem = 0
    foreach ($virtualmachine in $virtualmachines) {
         $TotalMem = $TotalMem + $virtualmachine.PhysicalMemoryAllocated
    }
    $TotalMem_in_MB = [math]::round($TotalMem/1mb,1)
    Write-Output "`tTotal mem used: `t $TotalMem ($TotalMem_in_MB MB)`n"
}

$b = GetVMmem; $b