Categories

9582

Getting mailbox data and stats per database

Way back in the day - http://richardspowershellblog.wordpress.com/2007/12/20/list-mailboxes-by-mailbox-database/ - I showed how to list mailboxes by the database in which they were stored.  I had a comment left asking if its possible to list only a specific mailbox and to give the mailbox size as well.

To recap:

Get-Mailbox will return the list of mailboxes

This will quickly show the number of mailboxes per database

Get-Mailbox | group Database –NoElement

This shows the mailboxes in a particular database

Get-Mailbox -Database MDB1

To get the size you use Get-MailboxStatistics

So to put this together:

function get-mbxBYdb {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$database
)

Get-Mailbox -Database $database |
foreach {
$stat = $_ | Get-MailboxStatistics -WarningAction SilentlyContinue
 
New-Object -TypeName PSObject -Property @{
   Name = $($_.DisplayName)
   Address = $($_.PrimarySmtpAddress)
   Database = $database
   Items = $stat.ItemCount
   'Size(KB)' = $stat.TotalItemSize.Value.ToKB()
}

}

}

The database name is a mandatory parameter.  Get the mailboxes in the database and foreach get the mailbox statistics.  You can then create an output object that combines the data from the mailbox object and the statistics object.  Examine each type of object individually to determine the exact set of properties you need.

One trick with the size of items is that you can convert to specific size units as shown (MB, GB, TB and bytes are also available)

End of PowerShell Jobs week

My series on PowerShell Jobs for the Scripting Guy blog has finished.  The set of articles is:

Jobs week 1 : Introduction to PowerShell jobs
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/02/powershell-jobs-week-introduction-to-powershell-jobs.aspx 
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/02/powertip-control-job-starts-with-powershell.aspx

Jobs week 2 : WMi and CIM jobs
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/03/powershell-jobs-week-wmi-and-cim-jobs.aspx 
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/03/powertip-use-powershell-to-keep-job-data.aspx 

Jobs week 3 : Remote Jobs
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/04/powershell-jobs-week-remote-jobs.aspx 
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/04/powertip-use-powershell-to-find-job-help.aspx 

Jobs week 4 : Scheduled Jobs
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/05/powershell-jobs-week-scheduled-jobs.aspx 
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/05/powertip-use-powershell-to-discover-tasks-associated-with-scheduled-jobs.aspx 

Jobs week 5 : Workflow Jobs
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/06/powershell-jobs-week-jobs-and-workflows.aspx 
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/06/powertip-stop-a-powershell-job.aspx 

Jobs week 6 : Job processes
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/07/powershell-jobs-week-job-processes.aspx
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/07/powertip-protect-the-data-produced-by-powershell-jobs.aspx

Jobs week 7 : Jobs in the enterprise
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/08/powershell-jobs-week-jobs-in-the-enterprise.aspx
http://blogs.technet.com/b/heyscriptingguy/archive/2014/03/08/powertip-find-more-information-about-windows-powershell-jobs.aspx 

 

Enjoy

CDXML–scripting creation

So far you’ve seen how to create CDXML files by hand – though you probably used cut and paste rather than typing everything from scratch.

Its time to bring a bit of automation to creating CDXML files. The XML is fairly simple and you’re only changing a couple of values so you can do this:

function new-cdxml {
[CmdletBinding()]
param (
[string]$class,
[string]$noun,
[string]$namespace = 'ROOT\cimv2',
[string]$path = "C:\Scripts\Modules\Hardware"
)

$code = @"
<?xml version='1.0' encoding='utf-8'?>
<PowerShellMetadata xmlns='http://schemas.microsoft.com/cmdlets-over-objects/2009/11'>
  <Class ClassName='$namespace\$class'>
    <Version>1.0</Version>
    <DefaultNoun>$noun</DefaultNoun>

    <InstanceCmdlets>
      <GetCmdletParameters DefaultCmdletParameterSet='DefaultSet'>
             
      </GetCmdletParameters>
    </InstanceCmdlets> 
  </Class>
 
</PowerShellMetadata>
"@

$file = Join-Path -Path $path -ChildPath "$class.cdxml"
Write-Verbose -Message  $file
Set-Content -Path $file -Value $code

}

I saved this as NewCDXML.ps1.  This will eventually  become the basis of a CDXML file creation module. I set defaults on the namespace and the path – feel free to change them if required.

The function is run as:

new-cdxml -class Win32_DiskDrive -noun PhysicalDisk

which produces this CDXML file

<?xml version='1.0' encoding='utf-8'?>
<PowerShellMetadata xmlns='http://schemas.microsoft.com/cmdlets-over-objects/2009/11'>
  <Class ClassName='ROOT\cimv2\Win32_DiskDrive'>
    <Version>1.0</Version>
    <DefaultNoun>PhysicalDisk</DefaultNoun>

    <InstanceCmdlets>
      <GetCmdletParameters DefaultCmdletParameterSet='DefaultSet'>
             
      </GetCmdletParameters>
    </InstanceCmdlets> 
  </Class>
 
</PowerShellMetadata>

 

The Hardware.psd1 file needs to be altered:

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = @('Win32_BIOS.cdxml',
                   'Win32_ComputerSystem.cdxml',
                   'Win32_DiskDrive.cdxml'
                    )

 

and

# Functions to export from this module
FunctionsToExport = @('Get-Bios',
                      'Get-ComputerSystem',
                      'Get-PhysicalDisk'
                      )

 

The module now looks like this:

£> Get-Command -Module Hardware

CommandType      Name
-----------                ----
Function                Get-Bios
Function                Get-ComputerSystem
Function                Get-PhysicalDisk

 

This makes creating additional components for your module much easier.

It’s the little things

I’ve been spending some quality time with PowerShell 2.0 recently. When it was all we had it was great but there’s a bunch of things I miss from PowerShell 3.0 including:

CIM cmdlets

CIM sessions

Workflows

Module auto loading

Improved tab completion

PowerShell 3.0 ISE especially the combined input/output panes

Of them all it’s the last three that I really miss the most. You become accustomed to the new features and how they make your life easier. To quote “You don’t know what you’ve got ‘til it’s gone”.

So very true but you appreciate them more when you get back to using them.

Creating DNS PTR records

When I was writing the DNS chapter of PowerShell in Practice I couldn’t get the CreateInstanceFromPropertyData  method on the MicrosoftDNS_PTRType  class to work. Revisiting DNS for AD management in a Month of lunches this time round I have access to the CIM cmdlets so can put the parameter names in.  This gives usage like this.  I’ve shown Invoke-WmiMethod and Invoke-CimMethod so you can see the parameter names:

Invoke-WmiMethod -Namespace root\MicrosoftDNS -Class MicrosoftDNS_PTRType `
-Name CreateInstanceFromPropertyData `
-ArgumentList "175.168.192.in-addr.arpa", 'server02', '55.175.168.192.in-addr.arpa',
"ADMLServer02.admldns.test" 

Invoke-CimMethod -Namespace root\MicrosoftDNS -ClassName MicrosoftDNS_PTRType `
-MethodName CreateInstanceFromPropertyData `
-Arguments @{Containername = "175.168.192.in-addr.arpa";
DnsServerName = 'server02'; OwnerName = '55.175.168.192.in-addr.arpa';
PTRDomainName ="ADMLServer02.admldns.test"}

 

If you have access to Windows 2012 then you are better off using the cmdlet

Add-DnsServerResourceRecordPtr –Name ‘54’ `
–ZoneName “175.168.192.in-addr.arpa” `
–PtrDomainName  ‘ADMLServer01.admldns.test’  `
–ComputerName server02

 

Which ever method you use – you can easily create PTR records

Displaying data from multiple servers as HTML

A forum question regarding retrieving WMI based data from multiple servers and displaying it as HTML was interesting.  I would approach it like this

$servers = Get-Content -Path C:\scripts\servers.txt            
$data = @()            
foreach ($server in $servers){            
 $compdata = New-Object -TypeName PSObject -Property @{            
  Computer = $server            
  Contactable = $false            
  LastBootTime = ""            
  AllowTSConnections = $false            
 }            
            
 if (Test-Connection -ComputerName $server -Quiet -Count 1){            
   $compdata.Contactable = $true            
               
   $os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $server            
   $compdata.LastBootTime = $os.ConvertToDateTime($os.LastBootUpTime)            
            
   $ts = Get-WmiObject -Namespace root\cimv2\terminalservices -Class Win32_TerminalServiceSetting -ComputerName $server -Authentication PacketPrivacy            
   if ($ts.AllowTSConnections -eq 1){            
    $compdata.AllowTSConnections = $true            
   }            
 }            
             
 $data += $compdata            
}            
$data            
$data | ConvertTo-Html | Out-File -FilePath c:\scripts\report.html            
Invoke-Item -Path c:\scripts\report.html


Put the list of servers in a text file & read it in via get-content.



use foreach to iterate over the list of servers.



For each server create an object and then test if you can ping the server. Note that the default setting for Contactable is $false so don’t need to deal with that case.



Get the WMI data and set the properties on the object.



Add the object to an array



After you’ve hit all the servers use ConvertTo-Html and write to a file with out-file.



use Invoke-Item to view the report

Ensuring that parameter values are passed to your function

A question on the forum about a function had me thinking. The user had defined two parameters for the function and then used Read-Host to get the values.

NO

Much better way is to use an advanced function and make the parameters mandatory

function Getuserdetails {            
[CmdletBinding()]            
param (            
[parameter(Mandatory=$true)]            
[string]$Givenname,             
            
[parameter(Mandatory=$true)]            
[string]$Surname            
)             
            
Get-ADUser -properties telephonenumber,office -Filter {(GivenName -eq $Givenname) -and (Surname -eq $Surname)}             
            
}


If you call the function and don’t give values for the parameters you will be prompted for them



The other point is the –Filter property on get-aduser.  Don’t put quotes round the variable

WMI property names

A question brought it home to me that WMI property names don’t always mean what you might think they mean – this is also true of other objects but I tripped over this one with WMI so we’ll stick with that.

PS> Get-CimInstance -ClassName Win32_Desktop -Filter "Name LIKE '%Richard'" |
>> Format-List ScreenSaver*
>>


ScreenSaverActive     : True
ScreenSaverExecutable : C:\Windows\system32\PhotoScreensaver.scr
ScreenSaverSecure     : True
ScreenSaverTimeout    : 600

 

The ScreenSaverActive property doesn’t mean that the screen saver is currently active – it means that one has been set!   This is doubly confusing because the documentation states

ScreenSaverActive
Data type: boolean
Access type: Read-only

Screen saver is active.

Which would lead you to think that is was actually running!

The ScreenSaverSecure means that a password has been set to unlock the system once the screen saver kicks in & ScreenSaverTimeout is the idle time in seconds before the screen saver kicks in. The executable is the screen saver that will be used.

If you want to get the information with the WMI cmdlets use

PS> Get-WmiObject Win32_Desktop -Filter "Name LIKE '%Richard'" |
>> Format-List ScreenSaver*
>>


ScreenSaverActive     : True
ScreenSaverExecutable : C:\Windows\system32\PhotoScreensaver.scr
ScreenSaverSecure     : True
ScreenSaverTimeout    : 600

Working with profiles: 2 deleting profiles

I recently (1 June) showed how to discover the user profiles on your system. Now its time to delete them.

function remove-profile {            
 param (            
  [parameter(Mandatory=$true)]            
  [string]$username            
 )            
            
 $user = Get-CimInstance -Class Win32_UserAccount -Filter "Name = '$username'"             
 $profile = Get-CimInstance -Class Win32_UserProfile -Filter "SID = '$($user.SID)'"            
 $folder = Split-Path -Path $profile.LocalPath -Leaf            
            
 if ($folder -eq $username){            
  Remove-CimInstance -InputObject $profile            
 }            
 else {            
  Write-Warning -Message "Could not resolve profile and user name"             
 }            
            
}


I’m going to start with the CIM cmdlets as these are the way of the future in PowerShell v3.



Start by taking a user name as a parameter. Get the Win32_UserAccount class object representing that account. use the SID to find the profile via Win32_UserProfile.  Take the profile’s localpath and split it. The last part of the path should match the username – if it does then delete the profile otherwise throw a warning. Deleting the profile does delete the folder under c:\users



If you have to use the WMI cmdlets then its very similar



function remove-profile {            
 param (            
  [parameter(Mandatory=$true)]            
  [string]$username            
 )            
            
 $user = Get-WmiObject -Class Win32_UserAccount -Filter "Name = '$username'"             
 $profile = Get-WmiObject -Class Win32_UserProfile -Filter "SID = '$($user.SID)'"            
 $folder = Split-Path -Path $profile.LocalPath -Leaf            
            
 if ($folder -eq $username){            
  Remove-WmiObject -InputObject $profile            
 }            
 else {            
  Write-Warning -Message "Could not resolve profile and user name"             
 }            
            
}


Just the name of the cmdlets change.



You can’t use WMI to delete local accounts as explained on page 363 of PowerShell and WMI 



If you have profiles generated by AD accounts you’ll need to find the SID from the AD account and use that as the filter for deletion

Using a colon with cmdlet parameters

Another question at Tuesdays PowerShell group revolved around using colons to link values to parameters. I’d not really thought about before. Thinking about it  later I realised that you only really see it when passing booleans to –Confirm.  If you don’t use a colon then you get this

PS> Disable-NetAdapter -Name "Virtual Wireless" -Confirm $false
Disable-NetAdapter : A positional parameter cannot be found that accepts argument 'False'.
At line:1 char:1
+ Disable-NetAdapter -Name "Virtual Wireless" -Confirm $false
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Disable-NetAdapter], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Disable-NetAdapter

But this will work

PS> Disable-NetAdapter -Name "Virtual Wireless" -Confirm:$false

Just for completeness this works as well

PS> Get-NetAdapter –Name:"Virtual Wireless" 

though it doesn’t tend to get used