Archive

Posts Tagged ‘Powershell’

Scripting Games Advanced – Event 3 My Submission

May 15th, 2013 No comments

Lets first jote down the Event Requirement:

AE3-Description-1

Input:

ComputerName(s) as string (from pipeline possible).

A Path to save output file.

Output:

An html file for each computer name provided.

The output should look like this:

AE3-Description-2

Additional Requirements:

  • Paramaterize the inputs
  • Use Pipeline for ComputerName
  • The browser displays “Disk Free Space Report” in the page tab when viewing the report.
  • “Local Fixed Disk Report” is in the H2 (“Heading 2”) HTML style.  If you can actually add the computer name to that – bonus!
  •  The report ends with an HTML horizontal rule and the date and time that the report was generated.
  • The size and free space values are shown as gigabytes (GB) and megabytes (MB) respectively, each to two decimal places.

Here is the break down of my script:

Requirement one is to get free space, capacity, and drive letter of “Fixed” disks on a given machine. we can use Win32_LogicalDisk WMI Class to get this information. to filter and take only fixed disks we can use “DriveType=3″ as filter. you can read more about this class here.

Here is my parameter definitions:

    Param
    (
        #ComputerName to Generate Report For
        [Parameter(ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Alias("CN","__SERVER","SERVER","host","PSComputerName","DNSHostName","MachineName")] 
        [String[]]
        $ComputerName = $env:COMPUTERNAME,

        #Path (Directory) to store the html file
        [Parameter()]
        [ValidateScript({Test-Path -Path $_ -PathType Container})]
        [String]
        $Path = [environment]::GetFolderPath(“MyDocuments”),

        #Credential to use if required
        [Parameter()]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty

    )
  • ComputerName is defined as a String Array, which accepts value from pipeline and by property name. There are also alisases defined here.  The default is set to local machine
  • Path is defined and a validation is done using Test-Path, the Default is set to Documents folder of the logged on user.
  • A third paramter Credential is defined as a feature to use if the target machine needs alternate credentials.

The Credential is added to the wmioptions hash table defined in the Begin Block based on its presence:

Begin {

        #Define WMI Query Options
        $WmiOptions = @{}
        $WmiOptions.Class = 'Win32_LogicalDisk'
        $WmiOptions.Property = 'Name','Size','FreeSpace'
        $WmiOptions.Filter = 'DriveType = 3'
        $WmiOptions.ErrorAction = 'Stop'
        $WmiOptions.ErrorVariable = 'WmiError'

        #Add Credential to the WMI Options if it was specified
        if ($PSBoundParameters['Credential']) {
               $wmiOptions.Credential = $Credential
        }

}

Credential is removed / preserverved based on the computer being processed, if its pointing to local machine credential is not used. we define an array for defining local hosts. and check the presense of computer name in it.

Begin {

$LocalIPAddresses = [net.dns]::GetHostAddresses($env:COMPUTERNAME) | Select-Object -ExpandProperty IpAddressToString
        $Localhost = @('', '.', 'localhost', $env:COMPUTERNAME, '::1', '127.0.0.1',"$env:COMPUTERNAME.$env:USERDNSDOMAIN") + $LocalIPAddresses

}

process {

#If Local Host Remove Credential from the Query Else Maintain/Add Credential to the Query
	            if ($Localhost -contains $computer) { $wmiOptions.Remove('Credential') } else {$wmiOptions.Add('Credential',$Credential)}

}

Next is the WMI Query:

$WmiOptions.ComputerName = $Computer

$DiskInfo = Get-WmiObject @WmiOptions

$DiskInfo = $DiskInfo | Select-Object Name,
                    @{n='Size(GB)';e={"{0:N2}" -f ($_.Size/1Gb)}},
                   @{n='FreeSpace(MB)';e={"{0:N2}" -f ($_.FreeSpace/1Mb)}}

Preparing and Generating and Saving the Output File:

$HtmlDataSection = @{}
$HtmlDataSection.As = 'Table'
$HtmlDataSection.PreContent =  "<h2>Local Fixed Disk Report For : $($Computer.ToUpper()) </h2>"
$HtmlDataSection.PostContent = "<hr/>`n$(Get-Date)" 
$HtmlDataSection.Head = "$css`n<title>Drive Free Space Report</title>"

$Html = $DiskInfo | ConvertTo-Html @HtmlDataSection
    $Html | Out-File -FilePath  $(Join-Path -Path $Path -ChildPath "$computer.html") -Force -ErrorAction Stop

I have also applied a short css style to the html report. and here is the output of my script:

AE3-Output

 

 

<#
.Synopsis
   This fucntion Get-DiskInfo Get Fixed Disks Free Space Report

.DESCRIPTION
   The Get-DiskInfo Collects disk info using WMI.
   It Only takes into account fixed disk.
   Fixed disks are identified using "DriveType" property of Win32_LogicalDisk WMI Instance.
   The Resulting Data is converted to a format suitable for html style display.

.PARAMETER ComputerName
    The computer name to collect data from, default is local machine.
    This can be a one or more computernames as strings

.PARAMETER Path
    The Directory Path to store resulting html file.
    Defaults to My Documents folder of currently logged on user.

.PARAMETER Credential
    The Credential parameter is used when specified for remote machines.
    This is useful when the logged on user doesnt have necessary rights to the target machine.

.EXAMPLE
   Get-DiskInfo
   This Provides the report of local machine as an html file in currently logged on users My Documents Folder

.EXAMPLE
   "RemoteHost","localhost","MyPC" | Get-DiskInfo -Path C:\Temp
   This Cmdlet will provide three html file output for each computer name provided and will be stroed in C:\Temp folder

.EXAMPLE
   "RemoteHost","localhost","MyPC" | Get-DiskInfo -Path C:\Temp -Credential Get-Credential
   This Cmdlet will provide three html file output for each computer name provided and will be stroed in C:\Temp folder
   In Addition it will use the alternate credential provided to query Remote PCs, for local Credential will not be used.

.INPUTS
   System.String

.OUTPUTS
   HTML File (No Output to Host Display)

.NOTES
   Version: 1.0

#>
function Get-DiskInfo
{

    [CmdletBinding()]

    Param
    (
        #ComputerName to Generate Report For
        [Parameter(ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [ValidateNotNullOrEmpty()]
        [Alias("CN","__SERVER","SERVER","host","PSComputerName","DNSHostName","MachineName")] 
        [String[]]
        $ComputerName = $env:COMPUTERNAME,

        #Path (Directory) to store the html file
        [Parameter()]
        [ValidateScript({Test-Path -Path $_ -PathType Container})]
        [String]
        $Path = [environment]::GetFolderPath(“MyDocuments”),

        #Credential to use if required
        [Parameter()]
        [System.Management.Automation.Credential()]
        $Credential = [System.Management.Automation.PSCredential]::Empty

    )

    Begin {

        Write-Verbose "Initializing Function Environment"

        #Define WMI Query Options
        $WmiOptions = @{}
        $WmiOptions.Class = 'Win32_LogicalDisk'
        $WmiOptions.Property = 'Name','Size','FreeSpace'
        $WmiOptions.Filter = 'DriveType = 3'
        $WmiOptions.ErrorAction = 'Stop'
        $WmiOptions.ErrorVariable = 'WmiError'

        #Add Credential to the WMI Options if it was specified
        if ($PSBoundParameters['Credential']) {
               $wmiOptions.Credential = $Credential
        }

        #Defining entries which will be pointing at the local host from which the funtion/script is called
        $LocalIPAddresses = [net.dns]::GetHostAddresses($env:COMPUTERNAME) | Select-Object -ExpandProperty IpAddressToString
        $Localhost = @('', '.', 'localhost', $env:COMPUTERNAME, '::1', '127.0.0.1',"$env:COMPUTERNAME.$env:USERDNSDOMAIN") + $LocalIPAddresses

        $css = @'
        <style type="text/css">

        body { font-family: Tahoma, Geneva, Verdana, sans-serif;background-color: #DDDDDD}

		h2 { color:#6699CC}

        table {font-family:"Trebuchet MS", Arial, Helvetica, sans-serif; width:50%; border-collapse:collapse; background-color: #abcdef;
			border: 3px solid #ffffff;}

        th {font-size:14px; border:1px solid #ffffff;  padding-top:5px; padding-bottom:4px; text-align:left; 
		background-color:#abcdff; color:#ffffff; font-weight: bold;}

		tr {font-size:12px; border:1px solid #ffffff; padding:3px 7px 2px 7px; margin: 3px; vertical-align: top; color: #000;}

        td {font-size:12px; border:1px solid #ffffff; padding:3px 7px 2px 7px; margin: 3px; vertical-align: top; color: #000;}

        hr {background-color:#123456; border-width:1; color:#000000; height:2px; line-height:0; text-align:left; width:50%;}

		</style>
'@

    }

    Process {

        foreach($computer in $ComputerName) {

            Write-Verbose "Working on Computer:$computer"

            if(Test-Connection -ComputerName $computer -Count 1 -Quiet) {

                #If Local Host Remove Credential from the Query Else Maintain/Add Credential to the Query
	            if ($Localhost -contains $computer) { $wmiOptions.Remove('Credential') } else {$wmiOptions.Add('Credential',$Credential)}

                Write-Verbose "Collecting Disk Info from $computer using WMI"
                #collect WMI Data from computer
                $WmiOptions.ComputerName = $Computer
                try {

                    $DiskInfo = Get-WmiObject @WmiOptions
                    $DiskInfo = $DiskInfo | Select-Object Name,
                                @{n='Size(GB)';e={"{0:N2}" -f ($_.Size/1Gb)}},
                                @{n='FreeSpace(MB)';e={"{0:N2}" -f ($_.FreeSpace/1Mb)}}

                }
                catch {

                    Write-Warning "Unable to fetch wmi data from $computer"
                    Write-Verbose "Unable to fetch wmi data from $computer, below error occured : `n $_"
                    Write-Verbose "Completed Processing $Computer"

                }

                #prepare output only if WMI call was successful
                if(-not $WmiError) {

                    Write-Verbose "Generating HTML File Stream to create output file"
                    #Generating HTML File Stream
                    $HtmlDataSection = @{}
                    $HtmlDataSection.As = 'Table'
                    $HtmlDataSection.PreContent =  "<h2>Local Fixed Disk Report For : $($Computer.ToUpper()) </h2>"
                    $HtmlDataSection.PostContent = "<hr/>`n$(Get-Date)" 
                    $HtmlDataSection.Head = "$css`n<title>Drive Free Space Report</title>"

                    $Html = $DiskInfo | ConvertTo-Html @HtmlDataSection

                }

                try {

                    Write-Verbose "Saving Output to File $(Join-Path -Path $Path -ChildPath "$computer.html")"
                    $Html | Out-File -FilePath  $(Join-Path -Path $Path -ChildPath "$computer.html") -Force -ErrorAction Stop
                    Write-Verbose "Completed Processing $Computer"

                }
                catch {

                    Write-Error "Unable to Save the Output File $(Join-Path -Path $Path -ChildPath "$computer.html")"
                    Write-Verbose "Completed Processing $Computer"

                }

            }
            else {

                Write-Error "Unable to reach $computer on Network"
                Write-Verbose "Completed Processing $Computer"

            }
        }

    }

    End {

        Remove-Variable -Name WmiOptions,LocalIPAddresses,Localhost,css -Force -ErrorAction SilentlyContinue

    }
}

 

You can download the complete script here.

Cheers

PSMag PowerShell Brain Teaser: finding list of active IP Addresses of Local Machine

November 28th, 2012 No comments

Today i read the brain teaser winner announcement at Powershell Mag, that I am the winner of the last week’s Brain Teaser. And the same is announced in next Brain Teaser post here :

 

Here is the description of the last brain teaser:

 

And my solution was this:

The cmdlet gives a complete IPAddress Object(s):

 

There are other options as well, a  nice one mentioned by Mike F. Robbins is [Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces().GetIPProperties().UnicastAddresses|% Address - read the post at PSMag for a complete list of answers and comments.

I am happy to be the winner, and awaiting for the book “Powershell 3.0 First Look” to give it a try.

————————–

I would like to expand this post to share my learning from participating in this brain teaser, below I will make note of the same.

 

How to find Members of a DotNet Class from Powershell:

[Net.DNS] | Get-Member – This gives the members of the host’s Runtime

lets try this:  [Net.DNS] | Get-Member * -Force

This is still showing Runtime Members

Now lets try this, this must work: [Net.DNS] | Get-Member -Static

Note: There are obsolete methods for Net.Dns class which are recommended to be avoided by Developers, not sure should it be followed in Powershell as well.

System.Net.DNS Class:

According to MSDN:

Provides simple domain name resolution functionality.

The Dns class is a static class that retrieves information about a specific host from the Internet Domain Name System (DNS).

The host information from the DNS query is returned in an instance of the IPHostEntry class. If the specified host has more than one entry in the DNS database, IPHostEntry contains multiple IP addresses and aliases.

Few Methods of interest from this class:

  • [Net.DNS]::GetHostAddresses()  – Returns IP Addreses for the specified host.
  • [Net.DNS]::GetHostByName() – Obsolete. Gets the DNS information for the specified DNS host name
  • [Net.DNS]::GetHostByAddress() – Obsolete. Creates an IPHostEntry instance from IP Address
  • [Net.DNS]::GetHostEntry() – Resolves an IP address or Hostname to an IPHostEntry instance
  • [Net.DNS]::GetHostName()  – Gets the host name of the local computer
  • [Net.DNS]::Resolve() – Obsolete. Resolves a DNS host name or IP address to an IPHostEntry instance

 

You can check the overload definition (What parameters, and how to pass them) which can be found by executing the Method without the parenthesis

 

Examples of few useful static methods from Net.Dns class:

GetHostAddresses:

This method gives all the IP Address (IPv4 and IPv6) assigned to the machine right now.

Even this “[net.dns]::GetHostAddresses($null)” will work for local machine.

 

GetHostEntry:

This method gives All the IP Address list of the machine provided by Name or IP Address.

It gives similar output as GetHostAddresses.

If the host name or IP Address provided cannot be found an error is thrown.

 

GetHostName:

This is a simple method, it just gives you  the local machine name.

Its very useful, I was trying to get a cmdlet for this to stop using hostname.exe

In all these cases we can run them on remote machines as well by using Powershell remoting, but that’s not required as how this works – we get the results by giving a remote machine IP Address or Machine Name.

 

That’s it for now, Enjoy the Power Of Shell!

 

Categories: Powershell Tags:

Exchange Server 2013 Makes It Easier: Finding Database Storage Information

November 23rd, 2012 1 comment

Q:Why a post on this, topic?

Ans:In few of my Exchange automation and reporting scripts I have to figure out storage statistics of databases such as total capacity, free space, free space percentage etc. And I find it more easier to discover such information in Exchange 2013.

In this post, I will show some sample Powershell snippets I used to get the statistics on Exchange 2010 and later we will see how to get the same information from Exchange 2013.

 

Finding Exchange 2010 Mailbox Database Storage Information:

Note: All below Cmdlets have to be executed from Exchange Management Shell.  If you use native powershell.exe and PSSession method without using Exchange Remoting module it may behave differently due to Deserialization.

Scenario 1: Database is hosted on a Lun/Disk with Drive Letter Assigned

 

1) Getting Database Details

$DB = "MBX-DB01"
$Database = Get-MailboxDatabase $DB
$DBServer = $Database.Server.Name
$DBLun = $Database.EdbFilePath.DriveName

 

2) Calling WMI to get the volume information

#Below cmdlet should be run in one line
$Lun = Get-WmiObject -Class WIn32_Volume -Filter "DriveLetter='$DBLun'" `
-ComputerName $DBServer -Property DriveLetter,Capacity,FreeSpace

3) Composing the useful information

$DiskName = $Lun.DriveLetter
 $DiskCapacity = [math]::round(($Lun.Capacity/1GB),2)
 $DiskFreeSpace = [math]::round(($Lun.FreeSpace/1GB),2)

In above two lines, we are converting the byte value to GB and rounding it to two decimal point

4) Finding Percentage of Free Space

$FreePercentValue = [math]::round(($DiskFreeSpace * 100 / $DiskCapacity),2)

Use above cmdlet if you want to further use the value for operations such as comparison or sorting.

Use below if your purpose is just to print or report, the difference is nothing but, in above method the actual value if it is 82.10 will be shown as 82.1 and in below method it will still show as 82.10 but the difference is below will give you a string value and you cannot do mathematical operations with strings.

$FreePercentage = "{0:N2}" -f $FreePercentValue

 

Scenario 2: Database is hosted on a Lun/Disk with MountPoint Access Path

1) Getting Database Information

 $DB = "MBX-DB02" $Database = Get-MailboxDatabase
 $DB $DBServer = $Database.Server.Name
 #We are not using drive letter here as there is no -
 #drive letter assigned to the volume.
 $DBLun = $Database.EdbFilePath.PathName

Now we have to make a correct calculation to refer to the mount point. The mounted folder information is stored in two properties of win32_volume instance. Those are Caption and Name.  But how do you know dynamically what is the mount volume path from just the complete path of a database, not possible.  Here you should know the importance of having standards for naming convention, if you have a unique naming convention for your Database Luns and Mount Volumes , you can use something like this. In my scenario the total length of the Mount Volume from the Edbfile path was 24. So I extract a substring to match with the mount volume. Secondly I am replacing every back slash with two backslash, this is not a must but if you are using WMI Query you need to provide two slashes and not one.

 

$DBLun = $DBLun.Substring(0,24)
$DBLun = $DBLun.Replace("\","\\")
$Lun = Get-WmiObject -Class WIn32_Volume -Filter "Name='$DBLun'" `
-ComputerName $DBServer -Property Name,Capacity,FreeSpace

There is no drive letter, so we record only the mounted volume path from the Name property.

$DiskName = $Lun.Name
$DiskCapacity = [math]::round(($Lun.Capacity/1GB),2)
$DiskFreeSpace = [math]::round(($Lun.FreeSpace/1GB),2)

 

Calculating Percentage Value

$FreePercentValue = [math]::round(($DiskFreeSpace * 100 / $DiskCapacity),2)
[string]$FreePercentage = "{0:N2}" -f $FreePercentValue

 

From above you can see, such a task would be not easy but manageable in a single on-premise or SMB environment. What about a large distributed enterprise or even in hosting environments. Here Exchange 2013 comes to rescue. You don’t have to make a single WMI call to get above described information. Everything is available from Exchange 2013 Mailbox Database Cmdlets.

 

A Quick look at Exchange 2013 Improvements in this Context:

I will try to show the changes by using the practical approach here. we will only explore the possibilities, it can be used in different ways according to your need.

$DB = "MBX-DB03"
$Database = Get-MailboxDatabase $DB
$DBServer = $Database.Server.Name
$DBCopyStatus = Get-MailboxDatabaseCopyStatus -identity $DB\$DBServer

 

Above you see I have used Get-MailboxDatabase cmdlet, which will give me the server name where the DB is currently mounted. Next I use Get-MailboxDatabaseCopyStatus cmdlet with -identity parameter in a form of DBName\ServerName – that is how a database copy is identified in DAG.

 

#Mountpoint or Drive Letter
$DBCopyStatus.DatabasePathIsOnMountedFolder
$DBCopyStatus.LogPathIsOnMountedFolder

 

The property DatabasePathIsOnMountedFolder and LogPathIsOnMOuntedFolder will tell you if the files are hosted on a Volume mounted to a folder or assigned with a drive letter. This is something very useful as to you no longer need to manually identify if the Database is using a mounted folder or  disk drive.

 

DatabaseVolumeMountPoint gives you the exact mounted folder or the drive letter

#DB Volume Mount Point or Drive Letter (Ends with "\" )
$DiskName = $DBCopyStatus.DatabaseVolumeMountPoint

 

LogVolumeMountPoint gives the Logs location

#Log Volume Mount Point or Drive Letter (Ends with "\" )
$DBCopyStatus.LogVolumeMountPoint

 

Surprisingly, you can even get the physical Disk Volume Name.

#Physical Disk Name
$DBCopyStatus.DatabaseVolumeName
$DBCopyStatus.LogVolumeName

 

Now lets collect other information we need for reporting or other automation scripts.

$DiskCapacity = $DBCopyStatus.DiskTotalSpace
$DiskFreeSpace = $DBCopyStatus.DiskFreeSpace
$DiskFreePercentage = $DBCopyStatus.DiskFreeSpacePercent

 

Unfortunately above three fields are only for DB Location, If you have DB and LOG on different LUN this cmdlet doesn’t give that ( at least as per my knowledge until I write this)

What I understand is most of the above information is added or useful for the new Autoreseed feature, and the three properties below as well.

 

#Properties relevant to Auto Reseed
$DBCopyStatus.LastDatabaseVolumeName
$DBCopyStatus.LastDatabaseVolumeNameTransitionTime
$DBCopyStatus.VolumeInfoError

 

Some information around the Numbers given by Exchange Cmdlet

DiskCapacity  and DiskFreeSpace are of  Microsoft.Exchange.Data.ByteQuantifiedSize Type, means which is a complete object with some methods and properties. So use Get-Member and look at it. By default the value is printed in a number + text format (a display friendly format) but that may not be what you always want to have.  there is a bit of calculation changes:

In My Lab Database:

!Assume DiskCapactiy is 26707226624 in Bytes.

$DiskCapacity.ToGB() will  give you 24

But if you do $DiskCapacity.ToBytes()  / 1GB will give you 24.87

For some calculations that .87 is not something you can avoid.

DiskFreeSpacePercentage is an integer (System.Int32) so nothing to worry there.

 

Few Tips:
 #Get a list of Active Database Copies on the currently connected server
 Get-MailboxDatabaseCopyStatus -Active
 #Get a list of All Active Database Copies on a particular server
 Get-MailboxDatabaseCopyStatus -Active -Server <ServerName>
 #To get some more useful Database Statistics `
 #Such as Physical size of the Database and `
 #AvailableNewMailboxSpace (This is not exactly the thing called whitespace) `
 #Below cmdlet is not new in Exchange 2013.
 Get-MailboxDatabase <DBName> -Status

I hope you got some insights, and this post was helpful.

I am happy to learn from you as well, please drop your feedback, comments, or suggestions in the comments area.

Enjoy Learning!