I am a Microsoft Senior Premier Field Engineer based out of Atlanta, GA. My focus is on Exchange Server and Powershell. This blog is mainly to share interesting Powershell script samples and tidbits about Exchange. I am a Microsoft Certified Master for Exchange Server 2007.
All postings provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at cpyright.htm.
Site Visitors:
When PowerShell v3 introduced the new simplified syntax for the where-object, at first I really loathed it
Get-Process | Where-Object {$_.ws -gt 100MB}
can be simplified to this:
Get-Process | Where ws -gt 100MB
The reason I didn't like it up front was because you still really have to understand the first more complex example because the simplified syntax can only do very simple comparisons. You cant do complex comparisons like joining two conditions with a –and operator.
It did finally grow on me though, and now, when no one is looking, I really love the newer simplified syntax. So much so that when I have a –and situation, I actually have caught myself lately using two adjacent where-object instead of one using the simplified syntax. I am sure there is ghastly performance impact of this, but for simple stuff, it works fine. Now this wouldn't work for the –or operator, but it works great for –and.
Get-Process | Where-Object {$_.ws -gt 100MB -and $_.pm -gt 100MB}
can be written using two simplified where-object cmdlets
Get-Process | Where ws -gt 100MB | where pm -gt 100MB
Is this wrong?
I think from a pure coding standpoint its not a good behavior, but PowerShell’s target audience is admins. PowerShell is a means to an end, it is not the end for admins. I think simplicity and readability are huge and because of that I don't think this is too wrong. You still cant get away without knowing the more complex –filterscript syntax, but this does seem more fun.
What do you think?
-Gary Siepser
Hey folks, I am like the Dos Equis guy. I don't post often, but when I do, its about cool stuff :)
Yours truly is going to be delivering two sessions at the upcoming PowerShell Saturday event at the Microsoft Office in Alpharetta. It should be a blast, and went really well in Ohio (Columbus) and North Carolina (Charlotte). Check out in info below. I am pretty sure there are still some more seatsleft, but don't wait too long!
PowerShell Saturday #003 will bring together 200 developers and IT professionals from Atlanta and the surrounding areas for a full day of intense learning and fun on October 27, 2012. With fifteen sessions covering a milieu of topics like Windows 8, SharePoint, SQL Server, Exchange, virtualization, Active Directory, and of course PowerShell, this event offers a unique opportunity to participate in a diverse community.
Click for more info and registration
Hey folks. I will be delivering a session the MS AD module for PowerShell at the February meeting of AAUG (Atlanta Active Directory User Group). Its open to all. These user groups are absolutely great. I have been started to attend the PowerShell user group in Atlanta when my travel schedule permits. If you want to see yours truly in action and learn about the AD Module, come on out. Check out the info on their site:
http://aadug.org/UpcomingMeeting.aspx
The meeting is Feb 16, 2012 from 7-9 at the Alpharetta MS Office. There meeting will be available via live meeting for their members (anyone can join). Come on out!
I don’t love doing the whole self promotion thing, but every so often I am proud of something enough to go for it. This past Christmas eve (Dec 24th…as if you didn’t know the date) “The Scripting Guy” Ed Wilson was nice enough to allow me to write a post for the “Hey Scripting Guy” Blog. It’s a pretty simple subject, but valuable just the same. Its about configuring Windows Search to index the actual contents of your PowerShell scripts/modules.
Check it out:
http://blogs.technet.com/b/heyscriptingguy/archive/2011/12/24/use-windows-search-to-find-your-powershell-scripts.aspx
I went through the MCM program for Exchange 2007 a few years ago. It was an absolutely great experience. Don’t get me wrong, I added some grey hair during those three weeks, but it was worth every minute and ounce of energy. I was fortunate enough to manage to pass all the exams and the qualification lab test on the first pass…which to me was huge. It means when you leave Seattle to come back home, you are DONE. It’s a great program run by some great folks. Its actually sort of funny for me to watch some of the folks in the video below as you get pretty close to the folks you interact with during that time, so now I just laugh at them on video. Either way though, their message is 100% true on on point! Check out this video and then fight for your spot to attend the MCM program and become a Master or Architect.
http://www.microsoft.com/showcase/en/us/details/eeb37636-e39c-47dc-a58c-c6deeb75fce3
Coming up next week is the 2011 Scripting Games. The last two years I have participated as a guest commentator. This year Scripting Guy Ed Wilson asked me to participate as a judge. So start sending bribes this way now…just kidding of course, I am un-corruptible.
Good luck with the games. If you are not familiar with them, they are a great way to have a task for PowerShell if you don’t have any real-world ones at the moment.
Alright, so I am being a little bit of a sales guy here, but yours truly is presenting the transport session May 24th here in Alpharetta, GA. This is a great event. Think of it sort of like a nice little one-day Tech-Ed with one tract. I was a part of the Platform Tech day last year at it was a very cool and well attended event. Below is the data sheet for the event. If you are interested in attending, contact your TAM (Technical Account Manager) for details and sign-up. Let me know if you are going to come and I will look for you.
Those of you that read my blog, mostly do so as you are trying to become better PowerShell’ers. This is an incredible opportunity to have actual tasks that will exercise your scripting muscles. its a great event, and yours truly might be assisting as a guest commentator this year. Check out the link below.
Grab this badge here!
In my experiences with Exchange and Powershell, I have definitely come across some interesting things. One of strangest…or so I thought…was the fact that whenever I looked at mailbox size it had a funny little “b” at the end and seemed to be in bytes. That little “b” though made converting that number to say MB or GB a little tricky. To see a mailbox size in Exchange 2007 or 2010, you mainly might want to use the “totalitemsize” property that is output for mailboxes when you run the “get-mailboxstatistics” cmdlet in Exchange Management Shell (EMS is just fancy branding for what I call POPS, plain old PowerShell, with the Exchange snap-in loaded, or remoted through RBAC in Exchange 2010).
The Totalitemsize property isnt shown by the default formatting of the objects output by get-mailboxstatistics, but if you format the output into a list or table and ask for that property you can see it. check out these screen shots:
No Formatting…
With formatting…
In the above screen shot you can see the totalitemsize property clearly shows a value, but there is that little “b”. Now in some regards, I like that “b” because it keeps away any confusion about what type of size unit that number is…clearly this is bytes.
So, what if we want to display this in MB or GB?
If we look at this from a traditional way, we might quickly think, lets just chop off that “b” and divide the number by the unit we need…something maybe like this: “(totalitemsize-without the b) / (1024 X1024)”. That would give us a version of the size in MB. This can be done, but you need to change this value to a string first in order to parse it out like a string. Its not that difficult to figure it out, but I am not going to show it here, because the point of this post is to educate on how to use these objects and value properly.
If you take just the totalitemsize property as shown below and pipe it to Get-Member (gm) you can see that the value is not simple string or number. Since there was a “b” showing, suspecting a normal number format might not have been a good guess. I might have though string, but either way, showing that “b” was just plain weird.
So…lets look at the value property shown above. Notice, that the value doesn't just contain a simple integer or other number object. It contains another weird looking Exchange .Net object. Hmmmm…lets take a look at that one through the good ole Get-Member microscope:
Now we are getting somewhere. Take a look at those methods above. So, now I am starting to realize that there is a method to this little “b” madness. It turns out that the number shown in bytes with that pesky “b” was simply the default .ToString() method representation of the totalitemsize property. It looks like we have methods to yield the value in whatever format we want.
With these simple methods we can get the totalitemsize in whatever format we want it to be in. Its literally built into the object. Now how do we use this from a practical standpoint. There are lots of approaches here, but I am thinking way ahead. Perhaps we want to look at lots of mailboxes at one time. Maybe we want to export that data to a CSV, or html. Maybe we want to pipe it to something else. Who knows. In order to keep things flexible, I like to maintain the integrity of the original objects that came out of the get-Mailboxstatistics in the first place. This brings me to my favorite choice, Add-Member.
Add-Member lets us modify objects, and in this case, we are simply going to add on a new property on to the output objects from the Get-MailboxStatistics. We are going to add on the totalitemsize in MB. Here is a pipeline style way to do this:
Get-Mailbox | Get-MailboxStatistics | Add-Member -MemberType ScriptProperty -Name TotalItemSizeinMB -Value {$this.totalitemsize.value.ToMB()} -PassThru | Format-Table DisplayName,TotalItem*
In this oneliner above and in the screen shot, you can see that this command will work against all mailboxes. If you simply leave off the formatting the objects are the original Microsoft.Exchange.Data.Mapi.MailboxStatistics object, but you can see below the scriptproperty we added on.
I hope this is helpful in understanding the deal behind that pesky “b” that shows in totalitemsize.
This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.
There are no real dependencies for this script, PSv1 or v2, no snap-ins, no modules….just plain old PS (POPS :) ). No magic happening here, just some basic read-host and ADSI in play. I wrote it for a colleague and thought I would share.
#This will probably only work for strings and simple numbers and such. $username = read-host "Enter a Username" $searcher = New-Object system.directoryservices.directorysearcher $searcher.filter = "(samaccountname=$username)" $searchresult = $searcher.FindAll() If ($searchresult.count -ne 1) { "A single SamAccountName was not matched properly" "$($searchresult.Count) records found" $searchresult return } $adobject = $searchresult[0].GetDirectoryEntry() "Found $($adobject.Get("DisplayName"))" [string]$attributename = read-host "Enter the name of the attribute you want to edit" $currentEAP = $ErrorActionPreference $ErrorActionPreference = "Silentlycontinue" "Current value of $attributename is " + $adobject.get($attributename) $ErrorActionPreference = $currentEAP $newvalue = Read-Host "Enter the new value (or hit Ctrl-C to Exit)" $adobject.Put($attributename,$newvalue) $adobject.SetInfo()
I hope this is useful to someone out there :)
When I teach PowerShell classes I sometimes get into a discussion on the more advanced topic (at least for a beginner class) of pipeline behavior in terms of running multiple elements of a pipe synchronously or asynchronously. I usually have a tough time demo’ing this in a really meaningful way until this week.
The challenge for me was, to make a demo that was very obvious, but have it not include any complication that would take student’s focus off of the demo. I didn't have a good cmdlet to run that would take a little while but deliver result objects steadily enough so I decided to make one….well a function at least.
First of all, I pre-load this function into my PowerShell prior to the demo:
PS C:\Users\gsiepser> function Get-ProcessSlowly { param([int]$delayinms=300) ; get-process | ForEach-Object {$_ ; Start-Sleep -Milliseconds $delayinms}}
By pre-loading this function, you can keep the confusion of this code away from the students so they can concentrate on the pipeline behavior. When you want to demo it, just use this like a cmdlet:
PS C:\Users\gsiepser> Get-ProcessSlowly
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
57 7 1460 4340 53 4524 ApMsgFwd
75 8 1892 5040 69 0.36 4584 ApntEx
126 11 2616 8808 81 1.00 3560 Apoint
……
I didn’t include the full output, but basically you will see that it outputs a line with a short delay (300ms is the default as you can see in the function). You can see in the function that you can use a different delay for more or less drama.
PS C:\Users\gsiepser> Get-ProcessSlowly -delayinms 1000
126 11 2616 8808 81 1.06 3560 Apoint ……
126 11 2616 8808 81 1.06 3560 Apoint
I tell them that they can imagine any cmdlet/script/function/whatever that takes some time to get its results back to the pipeline. Then I show them what a “sort-object” or an “-autosize” will do. It makes this a very obvious demo of how their pipe composition has a major effect on the user experience and timing of the code.
PS C:\Users\gsiepser> Get-ProcessSlowly | Sort-Object ws –Descending
Or
PS C:\Users\gsiepser> Get-ProcessSlowly | Format-Table –AutoSize
Both of these show you that you are stuck waiting until all of the processes are returned prior to being able to see anything in the host.
Anyway, this helped me out a bit and I hope it helps out someone out there. I thought I would share.
P.S. I am very sorry about how slow I have been posting lately. If anyone out there is actually reading this blog, send me a nasty-gram email :)
In the past two weeks I have really started playing with the new AD PowerShell cmdlets from Microsoft. I am really glad these cmdlets are finally here. I will admit though, to get them working in my lab, it was not easy. I cant blame this though on the cmdlets themselves, as my problems stemmed from the fact that I don't have 64-bit virtualization capability yet in my own lab.
To use the cmdlets you need to deal with the following 3 steps:
Once these three steps are in place you can then use the cmdlets. You can see the cmdlets a few ways, but perhaps the easiest is to do this: PS C:\> Get-Command -Module ActiveDirectory
By running the above cmdlet I found the following cmdlets:
Add-ADComputerServiceAccount Add-ADDomainControllerPasswordReplicationPolicy Add-ADFineGrainedPasswordPolicySubject Add-ADGroupMember Add-ADPrincipalGroupMembership Clear-ADAccountExpiration Disable-ADAccount Disable-ADOptionalFeature Enable-ADAccount Enable-ADOptionalFeature Get-ADAccountAuthorizationGroup Get-ADAccountResultantPasswordReplicationPolicy Get-ADComputer Get-ADComputerServiceAccount Get-ADDefaultDomainPasswordPolicy Get-ADDomain Get-ADDomainController Get-ADDomainControllerPasswordReplicationPolicy Get-ADDomainControllerPasswordReplicationPolicyUsage Get-ADFineGrainedPasswordPolicy Get-ADFineGrainedPasswordPolicySubject Get-ADForest Get-ADGroup Get-ADGroupMember Get-ADObject Get-ADOptionalFeature Get-ADOrganizationalUnit Get-ADPrincipalGroupMembership Get-ADRootDSE Get-ADServiceAccount Get-ADUser Get-ADUserResultantPasswordPolicy Install-ADServiceAccount Move-ADDirectoryServer Move-ADDirectoryServerOperationMasterRole Move-ADObject New-ADComputer New-ADFineGrainedPasswordPolicy New-ADGroup New-ADObject New-ADOrganizationalUnit New-ADServiceAccount New-ADUser Remove-ADComputer Remove-ADComputerServiceAccount Remove-ADDomainControllerPasswordReplicationPolicy Remove-ADFineGrainedPasswordPolicy Remove-ADFineGrainedPasswordPolicySubject Remove-ADGroup Remove-ADGroupMember Remove-ADObject Remove-ADOrganizationalUnit Remove-ADPrincipalGroupMembership Remove-ADServiceAccount Remove-ADUser Rename-ADObject Reset-ADServiceAccountPassword Restore-ADObject Search-ADAccount Set-ADAccountControl Set-ADAccountExpiration Set-ADAccountPassword Set-ADComputer Set-ADDefaultDomainPasswordPolicy Set-ADDomain Set-ADDomainMode Set-ADFineGrainedPasswordPolicy Set-ADForest Set-ADForestMode Set-ADGroup Set-ADObject Set-ADOrganizationalUnit Set-ADServiceAccount Set-ADUser Uninstall-ADServiceAccount
On the PowerShell Team Blog they posted a great write-up and a really nice graphic showing the cmdlets organized logically
I hope this is a short and sweet guide to help get the cmdlets working for you!
UPDATED: Minor Script Modifications made, detailed help comments added mainly for PSv2 and units added to property names
First of all, let me apologize as I have been posting very much lately. My plan with this blog was to post at least twice a week. Obviously my posting has slowed way down. I haven't at all lost interest in sharing with you, I just have run a bit low on ideas for the simpler, easier to write posts. These days, the subjects I post about are tending to be more complex scripts and by their nature, they take a lot longer to write. That being said, if anyone of the 3 of you out there reading this blog :) have any ideas for posts you would like to see, PLEASE comment on here or email me to ask for a post. I need subjects to post about that I know there is interest to read about.
Enough with the sob story…lets get to it.
This post is about a script I wrote last night to generate some mailbox database statistics that I think will be useful to many out there. I must thank Wayne Siegler as he was the inspiration for this script since he contacted me through this blog for help in writing something similar. Thanks Wayne for asking, as it got to me write this and be able to share it with others.
This script basically walks through your Exchange 2007 and above databases and gets their file size…but wait…there’s more. I already posted a one-liner to do that. This script though, then looks at the mailboxes on that database and generates stats about the total size of those mailboxes, the average size, and the max size. As well it gives you counts. It does this for all your databases and then at the end it simply looks at all the data its gathered so far and totals it all up to give you the org-wide stats. Again though, this right now only works against Exchange 2007 and above DBs. It could be modified to work against pre-Exchange 2007 DBs as well, but it would take a bit more code (the samples for what is needed can be found in others posts on this blog) and for now I want to keep the code a bit simpler.
Folks, like all my posts, this is no production-level script. I haven't built in any error checking, help information, parameters, etc. into this. This is meant to be a demonstration of some techniques that can be used to get this data and output it as an object.
Here is the code:
# .SYNOPSIS # This script gather statistics about Mailbox Databases # # .DESCRIPTION # This script gathers the following database statistics about Exchange # 2007 and above mailbox databases: # # - Mailbox Count on Database # - Database Physical FileSize # - Total of Mailbox Sizes # - Average Size of Mailboxes # - Largest Mailbox on Database # # The script also totals the gathered statistics from all databases # and adds the results to the end of the resultant array output. This # Script outputs a custom object to the success pipeline and its # results can be piped to any typical cmdlet like export-csv or out-file # # This script was created by Gary Siepser of Microsoft. This script is # provided "AS IS" with no warranties, and confers no rights. Use of # any portion or all of this script are subject to the terms specified # at http://www.microsoft.com/info/cpyright.htm. # # .INPUTS # None. You cannot pipe objects to this script # # .OUTPUTS # PSCustomObject with NoteProperties for each of the statistics # # .EXAMPLE # C:\PS> .\script.ps1 # # The script run alone will output in List format. # # .EXAMPLE # C:\PS> .\script.ps1 | Format-Table -Autosize # # This example will out a formatted table of the results. # # .EXAMPLE # C:\PS> .\script.ps1 | Export-Csv c:\export.csv # # This example will export the piplined object to a CSV file. # # .EXAMPLE # C:\PS> Get-Help .\script.ps1 -Full # # This example will retrieve the full help of this script. #Create an empty template object to be used as the blueprint of the final output objects $TemplateObject = New-Object PSObject | Select-Object DatabaseName,DatabaseFileSizeGB,TotalMailboxSizeGB,NumberofMailboxes,AverageMailboxSizeMB,MaxMailboxSizeMB #Create the empty array to hold the Output Results $ResultSet = @() #Retrieve all DB Objects and loop through them $databases = get-mailboxdatabase foreach ($DB in $databases) { #Create a copy of the Template $TempObject = $TemplateObject | Select-Object * #Get the DB File Size Info $DBRawFileInfo = get-wmiobject cim_datafile -computername $DB.server -filter ('name=''' + $DB.edbfilepath.pathname.replace("\","\\") + '''') $DBFileSize = [math]::Round([Decimal]( $DBRawFileInfo.filesize / 1GB),2) #Get Stats about the mailboxes in this database $AllMailboxstats = get-mailboxstatistics -database $DB.identity | Where-Object {$_.objectclass -eq "Mailbox"} $CombinedMailboxSizes = @() foreach ($mailbox in $AllMailboxstats) { $CombinedMailboxSizes += ($mailbox.totalitemsize.value.ToBytes() + $mailbox.totaldeleteditemsize.value.ToBytes()) } $Stats = $CombinedMailboxSizes | Measure-Object -Average -Maximum -Sum #Save all this data we have into the template object $TempObject.DatabaseName = $DB.Name $TempObject.DatabaseFileSizeGB = $DBFileSize $TempObject.TotalMailboxSizeGB = [math]::Round($Stats.sum / 1GB ,2) $TempObject.NumberofMailboxes = $Stats.count $TempObject.AverageMailboxSizeMB = [math]::Round($Stats.average / 1MB,2) $TempObject.MaxMailboxSizeMB = [math]::Round($Stats.maximum / 1MB,2) #Add the TempObject to the Result Array for final output $ResultSet += $TempObject } #Compute Organizational Totals From Existing ResultSet #Create a copy of the Template $TempObject = $TemplateObject | Select-Object * $AllDBFilesizeGB = $ResultSet | Measure-Object DatabaseFileSizeGB -Sum $AllMailboxSizeGB = $ResultSet | Measure-Object TotalMailboxSizeGB -Sum $AllMailboxCount = $ResultSet | Measure-Object NumberofMailboxes -Sum $AllAverageMailboxSizeMB = $ResultSet | Measure-Object AverageMailboxSizeMB -Average $AllMaxMailboxSizeMB = $ResultSet | Measure-Object MaxMailboxSizeMB -Max #Save all this data we have into the template object $TempObject.DatabaseName = "All Databases (Pre-Exchange 2007 not included)" $TempObject.DatabaseFileSizeGB = $AllDBFilesizeGB.Sum $TempObject.TotalMailboxSizeGB = $AllMailboxSizeGB.Sum $TempObject.NumberofMailboxes = $AllMailboxCount.Sum $TempObject.AverageMailboxSizeMB = $AllAverageMailboxSizeMB.Average $TempObject.MaxMailboxSizeMB = $AllMaxMailboxSizeMB.Maximum #Add the TempObject to the Result Array for final output $ResultSet += $TempObject $ResultSet
I hope this helps -Gary Siepser
I hope this helps
First of all let me thank Rich Doyle (a fellow PFE) for allowing me to post this script. Rich writes some pretty kick-butt stuff and I know I speak for many of us that appreciate his help and his scripts!
Now Rich claims he hacked this together and didn't get around to polishing it up with optimization and full error trapping and such. That is okay as my posts are all meant to be just conceptual demos and are not meant at all to be production level scripts. That said, this “unpolished” script is longer and way more thorough then what I usually write. You go Rich!
Here is Rich’s write-up on this script:
The customer I am at this week wanted an easy way for admins to find DC subnet membership if the subnet of the DC did not match what was defined for the local AD Site in which the DC was a member. The customer wanted finding the subnet in AD to be actionable by someone without extensive IP subnet knowledge - especially as there are a lot of defined subnets to review in AD.
Attached is a script that performs this search, and the results.
I am sending it out, as it provides some really useful functions for IPv4 Address, Network and Subnet calculations (IPv6 equivalent is coming soon). These are ported and slightly modified from some great c# examples on MSDN Blogs, referenced within the script. It also demonstrates some usage of the System.DirectoryService.ActiveDirectory.Forest class (and before anyone says anything, yes, I need to cache domains, domain controllers, sites and subnets data rather than re-search…but I haven’t gotten to optimization of the script yet J)
The script requires Powershell v1.0 (or 2.0, but not tested on the RTM release yet), and uses the following syntax:
.\Process-SubnetDefinitions.ps1 -serverlist:"server1","server2","server3"
The script checks the IP Address and subnet of the DC against the local site for an explicit network match, then all other sites for explicit network match. If there is no explicit network match it proceeds to check all subnets as "catch-alls", starting with 32-bit/4th octect subnets and working up to 8-bit/1st octet subnets until a match (if there is one) is found.
It will provide the following output:
PS C:\Documents and Settings\xxxx\My Documents\Conflicting Subnet Definitions> .\Process-SubnetDefinitions.ps1 -serverlist:"xxxx","xxxx"
***************************** Process-SubnetDefinitions.ps1 *****************************
Retrieving IP Address details and Subnet defintions for listed servers
Processing DC Local Sites First for explicit match....
Working on Server: xxxx
Processing All Other Sites for explicit match....
Working on Server: xxxx Checking Site: xxxx Match Found
DCName : xxxx IPAddress : 10.100.139.74 SubnetMask : 255.255.255.0 Site : xxxx SiteSubnets : {10.100.8.0/24, 172.28.22.0/24, 10.104.8.8/32, 10.100.139.33/32...} SiteWithMatchingSubnet : xxxx MatchingSiteSubnet : 10.100.139.0/24 MatchFound : True CriticalError : False ErrorDetails :
DCName : xxxx IPAddress : 10.104.225.1 SubnetMask : 255.255.255.0 Site : xxxx SiteSubnets : {10.104.225.36/32, 10.104.144.69/32, 10.179.2.20/32, 10.100.144.69/32...} SiteWithMatchingSubnet : xxxx MatchingSiteSubnet : 10.104.225.0/24 MatchFound : True CriticalError : False ErrorDetails :
Here is the script demo itself:
param([array]$serverlist=$null) # This script solved a specific problem for me - Identification of a Domain Controller's subnet membership in Active Directory (even if not in the same site, # or a "catch-all" subnet - where the IPV4 Network cannot be used, and IP Address Ranges must be used to determine). # It checks the local AD Site for network match, then all other AD Sites for network match, then all AD Sites for "catch-all" match # After some research, I found helpful articles on MSDN, and MSDN Blogs # This script demonstrates IPv4 Address calculations, enumerating Domains, DCs and Sites within Active Directory (and still needs to be optimized to not # re-query AD Data as it runs). # References # MSDN - IPAddress Class - http://msdn.microsoft.com/en-us/library/system.net.ipaddress(VS.80).aspx # MSDN - Forest Class - http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory.forest(VS.80).aspx # MSDN Blogs - Knom's Developer Corner - http://blogs.msdn.com/knom/archive/2008/12/31/ip-address-calculations-with-c-subnetmasks-networks.aspx # This script is provided "AS IS" with no warranties, and confers no rights. # Use of any portion or all of this script are subject to the terms specified at # http://www.microsoft.com/info/cpyright.htm. Write-Host ""; Write-Host "*****************************"; Write-Host "Process-SubnetDefinitions.ps1"; Write-Host "*****************************"; Write-Host ""; If($serverlist -eq $null) { Write-Host "You must specify the target dc using the serverlist switch. e.g., .\Process-SubnetDefinitions.ps1 -serverlist:'servername'"; break; } Write-Host "Retrieving IP Address details and Subnet defintions for listed servers"; $erroractionpreference = "SilentlyContinue"; $error.Clear(); # For use with [System.Net.IpAddress]::Parse which cannot read the native powershell byte array, so we Parse and create a string (returns a string, e.g. "255.255.255.0") function FormatBinaryMask { param([byte[]]$binaryMask) $string = [string]::Empty; for([int]$i = 0; $i -lt 4; $i++) { if($i -eq 0) { $string = $string + $binaryMask[$i].ToString(); } else { $string = $string + "." + $binaryMask[$i].ToString(); }; }; return $string; } # Specify the number of bits for the Host to return the subnet mask (returns an IPAddress object) function SubnetMaskByHostBitLength { param([int]$hostPartLength) $netPartLength = 32 - $hostPartLength; if ($netPartLength -lt 2) { Write-Error "Too many hosts for IPv4"; break; }; $binaryMask = New-Object byte[] 4; for([int]$i = 0; $i -lt 4; $i++) { if(($i * 8 + 8) -le $netPartLength) { $binaryMask[$i] = [byte]255; } elseif(($i * 8) -gt $netPartLength) { $binaryMask[$i] = [byte]0; } else { $oneLength = $netPartLength - $i * 8 $binaryDigit = [String]::Empty.PadLeft($oneLength, '1').PadRight(8, '0') $binaryMask[$i] = [System.Convert]::ToByte($binaryDigit, 2) }; }; return [System.Net.IPAddress]::Parse((FormatBinaryMask $binaryMask)); } # Specify the number of bits for the Network to return the subnet mask (returns an IPAddress object) function SubnetMaskByNetBitLength { param([int]$netPartLength) $hostPartLength = 32 - $netPartLength; return SubnetMaskByHostBitLength($hostPartLength); } # Performs a boolean AND between the IP Address and the subnet mask to return the Network Address, e.g. 192.168.1.0 (returns an IP Address object) function GetNetworkAddress { param([System.Net.IPAddress]$address, [System.Net.IPAddress]$subnetMask) [byte[]]$ipAdressBytes = $address.GetAddressBytes(); [byte[]]$subnetMaskBytes = $subnetMask.GetAddressBytes(); if ($ipAdressBytes.Length -ne $subnetMaskBytes.Length) { Write-Error "Lengths of IP address and subnet mask do not match"; break; }; $broadcastAddress = New-Object byte[] $ipAdressBytes.Length; for ([int]$i = 0; $i -lt $broadcastAddress.Length; $i++) { $broadcastAddress[$i] = [byte]($ipAdressBytes[$i] -band ($subnetMaskBytes[$i])); }; return [System.Net.IPAddress]::Parse((FormatBinaryMask $broadcastAddress)); } # Performs a boolean AND between the the subnet mask and 255.255.255.255 to return a filter for determining the number of usable addresses and those # limits within a network, e.g. would return 0.0.0.255 for the subnet mask 255.255.255.0 (returns an IP Address object) function GetNetworkUsableAddressRangeFilter { param([System.Net.IPAddress]$subnetMask) $topend = [System.Net.IPAddress]::Parse("255.255.255.255"); [byte[]]$subnetMaskBytes = $subnetMask.GetAddressBytes(); [byte[]]$topendBytes = $topend.GetAddressBytes(); $broadcastAddress = New-Object byte[] $subnetMaskBytes.Length; for ([int]$i = 0; $i -lt $broadcastAddress.Length; $i++) { $broadcastAddress[$i] = [byte]($topendBytes[$i] -bxor ($subnetMaskBytes[$i])); }; return [System.Net.IPAddress]::Parse((FormatBinaryMask $broadcastAddress)); } # Compares the networks of 2 sets of IP Address and subnet masks to determine if they are on the same network function { param([string]$address1, [string]$subnetMask1, [string]$address2, [string]$subnetMask2) $network1 = GetNetworkAddress ([System.Net.IPAddress]::Parse($address1)) ([System.Net.IPAddress]::Parse($subnetMask1)); $network2 = GetNetworkAddress ([System.Net.IPAddress]::Parse($address2)) ([System.Net.IPAddress]::Parse($subnetMask2)); return $network1.Equals($network2); } $results = @(); $forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest(); foreach($server in $serverlist) { $error.Clear(); $result = New-Object PSObject | Select-Object "DCName","IPAddress","SubnetMask","Site","SiteSubnets", "SiteWithMatchingSubnet","MatchingSiteSubnet","MatchFound","CriticalError","ErrorDetails"; $result.DCName = $server; $wmi = Get-WmiObject -computer $server Win32_NetworkAdapterConfiguration -filter "IPEnabled='true'"; If ($error) { $result.CriticalError = $true; $result.ErrorDetails = $error[0]; $error.Clear(); } else { $result.CriticalError = $false; $result.ErrorDetails = $null; $result.IPAddress = $wmi.IPAddress[0]; $result.SubnetMask = $wmi.IPSubnet[0]; }; foreach($domain in $forest.Domains) { foreach($dc in $domain.DomainControllers) { if($dc.Name.ToLower().Contains($server.ToLower())) { $result.Site = $dc.SiteName; }; }; }; foreach($site in $forest.Sites) { if($site.Name -eq $result.Site) { $result.SiteSubnets = $site.Subnets; }; }; $result.SiteWithMatchingSubnet = $null; $result.MatchingSiteSubnet = $null; $result.MatchFound = $false; $results += $result; }; # Search Local Site Subnets for explicit match Write-Host ""; Write-Host "Processing DC Local Sites First for explicit match...."; foreach($result in $results) { If($result.CriticalError -eq $false) { Write-Host ""; Write-Host (" Working on Server: {0}" -f $result.DCName); foreach($subnet in $result.SiteSubnets) { if($result.MatchFound -eq $false) { $res = $false; $netaddress = $subnet.Name.Split("/"); $res = IsInSameSubnet $result.IPAddress $result.SubnetMask $netaddress[0] (SubnetMaskByNetBitLength $netaddress[1]); If($res) { Write-Host " Match Found"; $result.SiteWithMatchingSubnet = $result.Site; $result.MatchingSiteSubnet = $subnet.Name; $result.MatchFound = $true; }; }; }; }; } $continue = $false; foreach($result in $results) { if($result.MatchFound -eq $false) { $continue = $true; }; } if($continue -eq $false) { $results; break; }; # Search All other Sites for explicit match Write-Host ""; Write-Host "Processing All Other Sites for explicit match...."; foreach($result in $results) { if($result.CriticalError -eq $false) { Write-Host ""; Write-Host (" Working on Server: {0}" -f $result.DCName); foreach($site in $forest.Sites) { $found = $false; if ($site.Name -ne $result.Site) { Write-Host (" Checking Site: {0}" -f $site.Name); foreach($subnet in $site.Subnets) { $res = $false; $netaddress = $subnet.Name.Split("/"); $res = IsInSameSubnet $result.IPAddress $result.SubnetMask $netaddress[0] (SubnetMaskByNetBitLength $netaddress[1]); If($res) { Write-Host " Match Found"; $result.SiteWithMatchingSubnet = $site.Name; $result.MatchingSiteSubnet = $subnet.Name; $result.MatchFound = $true; }; }; }; if($result.MatchFound) { break; }; }; }; }; $continue = $false; foreach($result in $results) { if($result.MatchFound -eq $false) { $continue = $true; }; } if($continue -eq $false) { $results; break; }; # Search all subnets for "catch-all" match Write-Host ""; Write-Host "Processing All Other Sites for Catch-All subnet match....if required...."; foreach($server in $results) { If($server.CriticalError -eq $false) { Write-Host ""; write-host (" Working on Server: {0}" -f $server.DCName); while($server.MatchFound -eq $false) { foreach($site in $forest.Sites) { if($server.MatchFound -eq $false) { Write-Host (" Checking Site: {0}" -f $site.Name); foreach($subnet in $site.Subnets) { if($server.MatchFound -eq $false) { $lastaddress = new-object byte[] 4; $netaddress = $subnet.Name.Split("/"); If($netaddress[0] -le 8) { $netmask = SubnetMaskByNetBitLength $netaddress[1]; $filter = GetNetworkUsableAddressRangeFilter $netmask; $i = 0; foreach($byte in $filter.GetAddressBytes()) { if ($byte -eq 0) { $lastaddress[$i] = [System.Net.IPAddress]::Parse($netaddress[0]).GetAddressBytes()[$i]; } else { $lastaddress[$i] = ([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[$i] + $byte; }; $i++; }; $serverip = ([System.Net.IPAddress]::Parse($server.IPAddress)).GetAddressBytes(); If(($serverip[0] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[0]) -and ($serverip[0] -le $lastaddress[0])) { If(($serverip[1] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[1]) -and ($serverip[1] -le $lastaddress[1])) { If(($serverip[2] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[2]) -and ($serverip[2] -le $lastaddress[2])) { If(($serverip[3] -ge $([System.Net.IPAddress]::Parse($netaddress[0])).GetAddressBytes()[3]) -and ($serverip[3] -le $lastaddress[3])) { Write-Host " Match Found"; $server.MatchingSiteSubnet = ("{0}/{1}" -f ($netaddress[0], $netaddress[1])); $server.SiteWithMatchingSubnet = $subnet.Site; $server.MatchFound = $true; }; }; }; }; }; }; }; }; }; }; }; }; $results; $erroractionpreference = "Continue";
Again Thanks a ton Rich!!!!! You really rock man!
-Gary
This sample script was written by a brilliant fellow PFE named Chris Schrimsher. Chris gave me permission to post this script out there to the two of you reading my posts…you know who you are :)
In this script there is a concept that he is using that I like. He uses a hash table (dictionary object), to build up a list of folders and their count of subfolders. As he works through the entire tree of public folders, he just checks to see if the parent is in the list. If its there, he just adds to the number count, if not he adds it with a 1. Its a very simple but brilliant use of a hash table.
I saw some of this script when he was writing it, and I must confess, I haven't actually run this version I am posting here. He prettied it up a bit and is also using a progress bar here.
Well, enough of me wasting time, here is the sample code that you can pull lots of neat scripting concepts from:
# Name: Get-PFSubFolderCount.ps1 # Author: Chris Schrimsher # Date: August 6, 2009 # # Description: # Enumerates all public folders, and generates a count of the number of subfolders that each folder has. # Warns about any folders that have more than the maximum specified below, and makes full report available. # This script is provided "AS IS" with no warranties, and confers no rights. # Use of any portion or all of this script are subject to the terms specified at # http://www.microsoft.com/info/cpyright.htm. $MaxSubFolders = 250 Clear-Host # Populate the initial progress bar (real time progress updating is not available while retrieving public folder information) Write-Progress -Activity "Public Folder Subfolder Count Tool" ` -CurrentOperation "Enumerating Public Folders, Please Wait..." ` -Status "This may take some time, especially in large organizations..." # Get all public folders and store in variable $PubFolders = Get-PublicFolder -Recurse -ResultSize Unlimited # Create hash table to hold results $SubFolderTable = @{} # Write-Host "Analyzing Public Folders and Building Results Table..." # Create a counter variable $Count = 0 # Enumerate through public folders one at a time $PubFolders | ForEach-Object ` { # Update the progress bar as each public folder is analyzed Write-Progress -Activity "Public Folder Subfolder Count Tool" ` -CurrentOperation "Analyzing Public Folders and Building Results Table..." ` -Status "Completion Status:" ` -PercentComplete ([int] (($Count / $PubFolders.Count) * 100)) # If the parent path is null, this is the root folder, so skip it if ($_.ParentPath -ne $null) ` { # If the parent path is not contained in the hash table, ... if ($SubFolderTable[$_.ParentPath] -eq $null) ` { # ... add it with a value of 1 (since this is the first subfolder of that parent) $SubFolderTable[$_.ParentPath] = 1 } else ` { # ... otherwise, increment the count of the existing parent path. $SubFolderTable[$_.ParentPath]++ } } # Increment the counter variable $Count++ } Write-Progress -Activity "Public Folder Subfolder Count Tool" ` -CurrentOperation "Counting folders with more than $MaxSubFolders subfolders..." ` -Status "Please wait..." # Write-Host "Counting folders with more than $MaxSubFolders subfolders..." $LargeFoldersCount = ($SubFolderTable.GetEnumerator() | Where-Object { $_.Value -gt $MaxSubFolders } | Measure-Object).Count if ($LargeFoldersCount -gt 0) { # Display count of folders with too many subfolders Write-Host "Number of folders with more than $MaxSubFolders subfolders: $LargeFoldersCount" # Display help information for table Write-Host "`nList of folders with more than $MaxSubfolders subfolders and subfolder count of each:" # Display hash table containing results sorted by number of subfolders, from largest to smallest $SubFolderTable.GetEnumerator() | Where-Object { $_.Value -gt $MaxSubFolders } | Sort-Object -Property Value -Descending | Format-Table # Display count of folders with too many subfolders Write-Host "`nNumber of folders with more than $MaxSubFolders subfolders: $LargeFoldersCount" Write-Host "`nFull output is available in the object `$SubFolderTable.`n" } else { # Display a message indicating no folders with too many subfolders were found Write-Host "`nNo folders found containing more than $MaxSubFolders subfolders." }
This is a script I put together because…well I don't remember what prompted me to write it. Who cares :)
This script uses the built-in cmdlets for Exchange 2007 find 2007 servers and legacy servers. It then uses WMI to talk to the legacy servers to get the mailbox sizes. Technically, this script requires Exchange 2007 Management tools to be installed to even get this kicked off. It wouldn't be very difficult to have it check for the existence or registration of the Exchange snap-in and behave differently if it were run on an a non Exchange 2007 server (or tools machine). It would require PowerShell to be installed. Moral of the story, it should be able to made to work against just Exchange 2000 and 2003 without 2007.
I am going to confess, I haven't been able to test this extensively. If anyone out there runs into issues with it, let me know and I can get it fixed up. I asked a few colleagues, but they all are bums and didn't test it for me. :)
Let me know what you think either way.
Here is the sample code:
# Script to get mailbox sizes from both Exchange 2007+ servers and legacy Exchange # 2000 and 2003 Servers # # This script is provided "AS IS" with no warranties, and confers no rights. # Use of included script samples are subject to the terms specified at # http://www.microsoft.com/info/cpyright.htm. # Written by: Gary Siepser, Microsoft, Premier Field Engineer
#Get list of only E2k7 or later mailboxes as we get their size through a cmdlet #, but the mailboxes still on E2K3 have to come from WMI $2007Users = Get-Mailbox -RecipientTypeDetails usermailbox
#Get a list of the legacy servers so we can connect to WMI on each one $legacyservers = get-exchangeserver | Where-Object {-not $_.IsExchange2007OrLater}
#get the stats on all the E2K7 Mailboxes. This may yield a number of red and yellow #errors and warnings respectively. These can be squashed through error and warning #preference. $2007Userstats = $2007Users | get-mailboxstatistics
#loop through the legacy servers to get the userstats through WMI foreach ($server in $legacyservers) { # Use the right WMI namespace and class to get to mailbox data and add it to a building # array to hold it all so we can combine this with the non-legacy user data already # collected $legacyusers += Get-WmiObject -ComputerName $server -Namespace root\MicrosoftExchangeV2 -Class Exchange_mailbox }
#Now we have all the data in two seperate variables. Unfortunately the objects types # in those variables is quite different. We are going to go ahead and create a simple # new object type and populate it with the data from each list to make a new common # list of mailbox names and sizes. #Create the object and give it the properties we want to use $templateobject = New-Object PSObject $templateobject = $templateobject | Select-Object Name,Server,MailboxSizeinMB
# Now we need to loop through the users lists and populate these custom objects # into a new array
#Create the empty array $results = @()
# Loop through the 2007 user stats first foreach ($user in $2007Userstats) { #copy the template object to this actual instance we want to add to the building list $newobjectInstance = $templateobject | Select-Object *
#Populate the properties with the data $newobjectInstance.Name = $user.DisplayName $newobjectInstance.Server = $user.ServerName
#We need to pull out the size in Bytes from the object that is in totalitemsize # and then devide it by 1MB and round it off. This gives us a nice number with # two decimals places...nicer that what the .ToMB() method gives us) $newobjectInstance.MailboxSizeinMB = [Math]::Round(($user.totalitemsize.value.ToBytes() / 1MB),2)
#add this object isntance to the result array $results += $newobjectInstance }
#now loop through the WMI data we got back for the legacy servers and users foreach ($user in $legacyusers) { #copy the template object to this actual instance we want to add to the building list $newobjectInstance = $templateobject | Select-Object *
#Populate the properties with the data $newobjectInstance.Name = $user.MailboxDisplayName $newobjectInstance.Server = $user.ServerName
#We need to pull out the size in Bytes from the object that is in totalitemsize # and then devide it by 1MB and round it off. This gives us a nice number with # two decimals places...nicer that what the .ToMB() method gives us) $newobjectInstance.MailboxSizeinMB = [Math]::Round(($user.size / 1MB *1KB),2)
# Sort the results in a more meaningful way $results = $results | Sort-Object mailboxsizeinMB –Descending
#Do whatever you want now, in this case simply output the results. $results
I had a previous customer shoot me an email asking for help whipping up a script to convert a list of IP addresses from a text file to their respective host names, and put that into another text file. I put together a little demonstration script to show a way to get this done. I am using the .Net Static Class “system.net.DNS”. I hope this is useful to someone :)
# The following line read a plain list of IPs from files. For this demo, I have
# this line commented out and added a line to just define an array of IPs here
#$listofIPs = Get-Content c:\IPList.txt
$listofIPs = "173.136.234.58","173.136.234.59","173.136.234.60"
#Lets create a blank array for the resolved names
$ResultList = @()
# Lets resolve each of these addresses
foreach ($ip in $listofIPs)
{
$result = $null
$currentEAP = $ErrorActionPreference
$ErrorActionPreference = "silentlycontinue"
#Use the DNS Static .Net class for the reverse lookup
# details on this method found here: http://msdn.microsoft.com/en-us/library/ms143997.aspx
$result = [System.Net.Dns]::gethostentry($ip)
$ErrorActionPreference = $currentEAP
If ($Result)
$Resultlist += [string]$Result.HostName
}
Else
$Resultlist += "$IP - No HostNameFound"
# If we wanted to output the results to a text file we could do this, for this
# demo I have this line commented and another line here to echo the results to the screen
#$resultlist | Out-File c:\output.txt
$ResultList
Such a simple concept…how can we just retrieve a listing of folders instead of files and folders. Hopefully readers of this post know already that in PowerShell (1 or 2) we use “Get-ChildItem” (many still use the alias “dir”) to retrieve a listing of files and folders. If you didn't know that “dir” was an alias for “Get-Childitem”, now you do :)
When I first learned this many moons ago, I wondered why the actual cmdlet had the word “item” instead of “file” or “folder”. Once I learned about PowerShell providers I realized that “get-childitem” was a more general cmdlet that can be used on all sorts of different data sources, totally dependant on what providers you have installed and loaded into your PowerShell host. Lets get back to the point here, again, we use “get-childitem” to get a simple directory listing:
No problem. Now, how do we list just the folder or directories? In CMD I would use a “dir /ad” to get this:
In the image above, you might notice a few more folders than we see in the PowerShell image above. This is because “get-childitem” doesn't show hidden files or folders by default, but we can see them by simply adding the "-force” switch to the cmdlet:
Confusion over. Now, back to just showing folders only. If you look at the parameter help for “get-childitem” you wont find a nice simple switch to show only containers…don't get me wrong, I wish it was there. Does this mean we are out of luck. Of course not, if we were then why are you still reading :)
So where does this leave us? PowerShell has so much power and grace through the use of these wonderful object rich pipelines. Lets use them to solve this simple need. We can leverage our old trusty object instance filtering cmdlet…”where-object”. We see that “get-childitem” is giving us more than what we need. Lets filter down the instances here so that we only have folders left. Now here is where the cat gets scared. There are a lot of ways to do this.
First my preferred way. If you look at a “get-member” result of a “get-childitem”, or simply pipe it to a “format-list *”, you will see that both the file and directory objects have a nice property called “PSIsContainer”. Its a Boolean (true or false) object contained in the property. This is great. We have a simple true or false property that lets us programmatically differentiate between folders and files.
In the image above you can see that clearly we are now just seeing folders. But wait…there’s more… :)
If you didn't spot or find the PSIsContainer property, there are others ways to do this. From a plain old “dir”, we can see the left most column is “mode”. While I wouldn't personally prefer mode to use for a filter, it can be done. If you send the “dir”, or “get-childitem” results to a get-member and really look at the mode member things get interesting. I PowerShell v1, “mode” is a “scriptproperty” and you can actually see that the code is a handful of if statements that are reading attributes from the attributes property. In PowerShell v2, it looks like “mode” was changed to a “CodeProperty”. To be perfectly honest, I am not sure why that changed, or what the implications of that change are. Often times in PowerShell I tell folks, you just dont have to know everything about what you are doing to be good with this :)
In the image below I will filter folders based on the mode property:
This works out pretty well. This examples uses basic wildcard matching with the “–like” operator. I am not a real fan of using text based properties for this sort of thing. This is why my preferred method is using the Boolean “PSISContainer” as I used above.
If you notice as well, from the above images, folder appear to have a null length. Files seem to have at least a valid size or 0. We can filter on this as well:
That seems to work. If you take a look at a “Get-Childitem” result piped to a “format-list *”, you will see that you can use the attributes property as well too. This property is a weird one at first glance. It turns out that this is a special object which really contains a property called “__Value” that has a bit masked value containing the attribute flags. When we see this property during a “get-ChildItem” though, we see its “.ToString()” method representation which converts the bitmask into a comma separated strings that are more human friendly.
Nonetheless, we can use either the “value__” or the string representation to filter on to list only folders:
If that isn't enough ways to do this, I have on more that I do think is another cool way to do this. When you pipe the results of the “Get-ChildItem” to a “get-member”, you see that there are actually two different object types that comes back. There are different objects for files and directories. We can use this to. We can use the type operator “-is” to filter out object types from our pipeline:
So you can see from this post, that when you consider such a simple need as displaying only folders, that poor old cat just doesn't have a chance. The fact that there are so many ways to accomplish this is both a blessing and a curse for those beginning learners. I love this flexibility as it breeds innovative ideas. Now just for the record, of all those ways above, in real life the only two I would ever use as the first one and the last one (the “PSISContainer” and the “-is [system.io.directoryinfo]”). They are more prone to accuracy and less chance of unexpected results…otherwise known as bugs:)
Have a good one!
This code is not really complicated at all. Just the same it can come in very handy, and some folks have never played with the Certificate provider. I have provided a one-liner version of this code, and a script version. They are basically the same, but certainly the script one is considerably more verbose and easy to read.
A note for the advanced: I have not yet been able to figure out if there is a way that you can construct the .Net objects that are instantiated here, but bind them to certificates on remote machines. When I looked at the MSDN documentation for this, I don't see a constructor for a remote machine. I still suspect there is a way to do, I just don't know it right now. If anyone knows it, contact me and I will be happy to write another post explaining how to use it…once I figure it out :)
One-liner: get-childitem cert:\LocalMachine -Recurse | where-object {$_.hasprivatekey -and $_.notafter -gt ((get-date).AddDays(-30)) -and $_.notafter -lt ((get-date).AddMonths(2))} | Sort-Object notafter | format-table subject,friendlyname,notafter -Autosize Script: # Script to Find Certs Expiring Soon # Written by: Gary Siepser, Microsoft # Variable Pre-Sets Section # Modify the varibale below to control how far into the future this script looks into the future $FutureDays = 60 # Modify the variable below to control how far into the past we look for expired certificates # Use a negative number for the past and 0 for now $PastDays = -30 # Main Code body below # Set up a variable with a datetime object representing right now $now = Get-Date # Calculate a new datetime object that represents the past $Past = $now.AddDays($PastDays) # Calculate a new Datetime object that represents the future $Future = $now.AddDays($FutureDays) # Create an array of all the certificates on the local system $certs = get-childitem cert: -Recurse # Filter the cert list down to only those that we have a private key, this ignores the hundreds # of preinstalled certs on a machine for the internet wide PKI $certswithKey = $certs | Where-Object{$_.HasPrivateKey} # Filter the filterd list down to those whose expiration date falls within the desired range $expiringcerts = $certswithKey | Where-Object {$_.notafter -ge $Past -and $_.notafter -le $future} # End Main Code Body #The line below simply presents the filtered list. You can alter this as you see fit $expiringcerts | sort-object notafter | Format-Table subject,friendlyname,notafter -AutoSize
One-liner:
get-childitem cert:\LocalMachine -Recurse | where-object {$_.hasprivatekey -and $_.notafter -gt ((get-date).AddDays(-30)) -and $_.notafter -lt ((get-date).AddMonths(2))} | Sort-Object notafter | format-table subject,friendlyname,notafter -Autosize
Script:
# Script to Find Certs Expiring Soon
# Written by: Gary Siepser, Microsoft
# Variable Pre-Sets Section
# Modify the varibale below to control how far into the future this script looks into the future
$FutureDays = 60
# Modify the variable below to control how far into the past we look for expired certificates
# Use a negative number for the past and 0 for now
$PastDays = -30
# Main Code body below
# Set up a variable with a datetime object representing right now
$now = Get-Date
# Calculate a new datetime object that represents the past
$Past = $now.AddDays($PastDays)
# Calculate a new Datetime object that represents the future
$Future = $now.AddDays($FutureDays)
# Create an array of all the certificates on the local system
$certs = get-childitem cert: -Recurse
# Filter the cert list down to only those that we have a private key, this ignores the hundreds
# of preinstalled certs on a machine for the internet wide PKI
$certswithKey = $certs | Where-Object{$_.HasPrivateKey}
# Filter the filterd list down to those whose expiration date falls within the desired range
$expiringcerts = $certswithKey | Where-Object {$_.notafter -ge $Past -and $_.notafter -le $future}
# End Main Code Body
#The line below simply presents the filtered list. You can alter this as you see fit
$expiringcerts | sort-object notafter | Format-Table subject,friendlyname,notafter -AutoSize
Like all my posts, this is just a demonstration sample. I hope some folks out there find this useful.
In my previous post (Some Handy Exchange Mailbox and Database Size Powershell One-Liners), I introduced a handy one-liner for looking up current mailbox database sizes. In Exchange 2007, the physical size of the database file isn't listed when you run a cmdlet like “get-mailboxdatabase”. This piece of information of course is of great interest to many admins out there. My first post worked pretty well against stand-alone Exchange servers, but had issues with clusters and 2008. Also, I used my no less preferred method to add the database size to an output object but using “Select-object”. In this one-liner, I have made a few updates to this one-liner that preserves the original .Net mailboxdatabase object by using “add-member”, and appears to work pretty well with clusters, 2008, stand-alone servers, mount points, …whatever…so far.
Here is one-liner. You will see that now I am using “add-member” to add a “noteproperty” to the objects in the pipeline. I ended up doing something slightly abnormal for me. I performed the “add-member” within a “foreach-object” code block. Also I added a membertype of “noteproperty” instead of my typical “scriptproperty”. The truth is both strategies worked fine and yeilded the results I was looking for. What I discovered during my testing was that if I used a “scriptproperty” there is no error reporting whatsoever if we encounter any errors with the code…in this case a WMI call. Unfortunately, since we are using WMI, there is a good chance a server may not respond cleanly or quickly. If WMI times out, then it makes for a poor user experience with zero explanation as to why the data shows up as a big fat 0. By using a note-property, I can keep the code block out of the .Net member and thus we get to see errors that occur. I had to use a “foreach-object” loop though to pull this off as now “Add-member” could no longer work in native pipeline mode.
The second fix, is now instead of the using the filesystem provider in PowerShell to retrieve the file size of the database file, I am now using WMI. Why did I make this choice…simple, I wanted to be able to query the file using the local file path that comes out of the “edbfilepath” properpty on the “mailboxdatabase” objects returned by the “get-mailboxdatabase” cmdlet. Why use the local path…because that gets me around the path translation I was doing in the previous version of this one-liner. That translation was why this thing didn't work against clusters. Now, I simply use WMI to remote the file lookup and thus I can use the local file path, which works now regardless of cluster, mount point, whatever. I did run into the issue though of having to escape the “\” (backslash) by using them twice in the filter string for the “get-wmiobject” cmdlet, thus you see the replace() method in use.
Special thanks to a few folks that were involved in motivating, testing, and helping me with this code. Brian Scott, for effectively slapping me across the face about using the filter parameter of “get-wmiobject”, and helping me find CIM_Datafile. John Carder for motivating me to write this new version and for being a quick tester in his environment where my v1 wouldn't work against his clusters, but this version does.
Enough talk, here is the one-liner:
Get-MailboxDatabase | foreach-object {add-member -inputobject $_ -membertype noteproperty -name mailboxdbsizeinGB -value ([math]::Round(([int64](get-wmiobject cim_datafile -computername $_.server -filter ('name=''' + $_.edbfilepath.pathname.replace("\","\\") + '''')).filesize / 1GB),2)) -passthru} | Sort-Object mailboxdbsizeinGB -Descending | format-table identity,mailboxdbsizeinGB
Like most of my scripts, I have not extensively tested this as it is merely a demonstration sample and not a production level script. If you leverage this example to write your own production level code, I would love to hear feedback on how it works for you. Please, leave comments or email me for anything regarding this post.
Today I was asked why this person saw more properties showing with a “format-table”, than with a “format-table *”. I will admit, I wasn't sure. I kicked myself later once I got the answer and I realized I should have known this. There is a PFE colleague of mine that always seems to be AMAZING at answering our questions when we ask them. He took maybe 30 minutes to school me on this.
Here is an example of what I am talking about:
PS C:\> (resolve-path HKLM:\SOFTWARE\Microsoft).provider | format-list * ImplementingType : Microsoft.PowerShell.Commands.RegistryProvider HelpFile : System.Management.Automation.dll-Help.xml Name : Registry PSSnapIn : Microsoft.PowerShell.Core Description : Capabilities : ShouldProcess Home : Drives : {HKLM, HKCU} PS C:\> (resolve-path HKLM:\SOFTWARE\Microsoft).provider | format-list Name : Registry Drives : {HKLM, HKCU} Path : Home : Description : Capabilities : ShouldProcess ImplementingType : Microsoft.PowerShell.Commands.RegistryProvider AssemblyInfo :
You’ll notice that in the second try, that there is a property showing that is called “assemblyinfo” that doesn't show in the first. I always though that a format-list * should show everything.
Here is Rich Doyle’s response to me when I asked this (Rich is a PFE like me):
If you weren't aware, the default formatting for viewing of types are defined in the following files: PS C:\> get-childitem $pshome\*.ps1xml Directory: Microsoft.PowerShell.Core\FileSystem::C:\Windows\System32\WindowsPowerShell\v1.0 Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 11/21/2006 11:47 AM 22120 Certificate.format.ps1xml -a--- 11/21/2006 11:47 AM 60703 DotNetTypes.format.ps1xml -a--- 11/21/2006 11:47 AM 19730 FileSystem.format.ps1xml -a--- 11/21/2006 11:47 AM 250197 Help.format.ps1xml -a--- 11/21/2006 11:47 AM 65283 PowerShellCore.format.ps1xml -a--- 11/21/2006 11:47 AM 13394 PowerShellTrace.format.ps1xml -a--- 11/21/2006 11:47 AM 13540 Registry.format.ps1xml -a--- 11/21/2006 11:47 AM 129836 types.ps1xml If you look inside of PowerShellCore.format.ps1xml you will find the following view definition for System.Management.Automation.ProviderInfo: <View> <Name>provider</Name> <ViewSelectedBy> <TypeName>System.Management.Automation.ProviderInfo</TypeName> </ViewSelectedBy> <ListControl> <ListEntries> <ListEntry> <ListItems> <ListItem> <PropertyName>Name</PropertyName> </ListItem> <ListItem> <PropertyName>Drives</PropertyName> </ListItem> <ListItem> <PropertyName>Path</PropertyName> </ListItem> <ListItem> <PropertyName>Home</PropertyName> </ListItem> <ListItem> <PropertyName>Description</PropertyName> </ListItem> <ListItem> <PropertyName>Capabilities</PropertyName> </ListItem> <ListItem> <PropertyName>ImplementingType</PropertyName> </ListItem> <ListItem> <PropertyName>AssemblyInfo</PropertyName> </ListItem> </ListItems> </ListEntry> </ListEntries> </ListControl> </View> Now, looking at an object of type System.Management.Automation.ProviderInfo using Get-Member we can see all of the Properties that are available on the object: PS C:\> (resolve-path HKLM:\SOFTWARE\Microsoft).provider | get-member TypeName: System.Management.Automation.ProviderInfo Name MemberType Definition ---- ---------- ---------- Equals Method System.Boolean Equals(Object obj) GetHashCode Method System.Int32 GetHashCode() GetType Method System.Type GetType() get_Capabilities Method System.Management.Automation.Provider.ProviderCapabilities get_Capabilities() get_Description Method System.String get_Description() get_Drives Method System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSDriveInf... get_HelpFile Method System.String get_HelpFile() get_Home Method System.String get_Home() get_ImplementingType Method System.Type get_ImplementingType() get_Name Method System.String get_Name() get_PSSnapIn Method System.Management.Automation.PSSnapInInfo get_PSSnapIn() set_Description Method System.Void set_Description(String value) set_Home Method System.Void set_Home(String value) ToString Method System.String ToString() Capabilities Property System.Management.Automation.Provider.ProviderCapabilities Capabilities {get;} Description Property System.String Description {get;set;} Drives Property System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSDriveInf... HelpFile Property System.String HelpFile {get;} Home Property System.String Home {get;set;} ImplementingType Property System.Type ImplementingType {get;} Name Property System.String Name {get;} PSSnapIn Property System.Management.Automation.PSSnapInInfo PSSnapIn {get;} As we can see, AssemblyInfo is actually not a property of the object. So, what is happening here is: (resolve-path HKLM:\SOFTWARE\Microsoft).provider | format-list * Piping the output to format-list * assumes inherent usage of the properties switch, and, just as with Get-Member enumerating properties, format-list is reading all of the properties from the object returning a nice list of their names and values - correct behavior. (resolve-path HKLM:\SOFTWARE\Microsoft).provider | format-list Piping the output to format-list (no properties or properties wildcard) invokes the default View definition in PowerShellCore.format.ps1xml, which enumerates what is defined and the values of those Properties, formatting them in a list. This, again, is the correct behavior, it's just that the default View contains the property AssemblyInfo that is not present on this instance of this System.Management.Automation.ProviderInfo object. Therefore, we see the property showing in the result, of course with no value as the property is not really there. This is exactly how the .Net Type system is supposed to work, just the same as if you typed “format-list name,assemblyinfo”, which would show the empty looking property.
If you weren't aware, the default formatting for viewing of types are defined in the following files:
PS C:\> get-childitem $pshome\*.ps1xml
Directory: Microsoft.PowerShell.Core\FileSystem::C:\Windows\System32\WindowsPowerShell\v1.0
Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 11/21/2006 11:47 AM 22120 Certificate.format.ps1xml -a--- 11/21/2006 11:47 AM 60703 DotNetTypes.format.ps1xml -a--- 11/21/2006 11:47 AM 19730 FileSystem.format.ps1xml -a--- 11/21/2006 11:47 AM 250197 Help.format.ps1xml -a--- 11/21/2006 11:47 AM 65283 PowerShellCore.format.ps1xml -a--- 11/21/2006 11:47 AM 13394 PowerShellTrace.format.ps1xml -a--- 11/21/2006 11:47 AM 13540 Registry.format.ps1xml -a--- 11/21/2006 11:47 AM 129836 types.ps1xml
If you look inside of PowerShellCore.format.ps1xml you will find the following view definition for System.Management.Automation.ProviderInfo:
<View> <Name>provider</Name> <ViewSelectedBy> <TypeName>System.Management.Automation.ProviderInfo</TypeName> </ViewSelectedBy> <ListControl> <ListEntries> <ListEntry> <ListItems> <ListItem> <PropertyName>Name</PropertyName> </ListItem> <ListItem> <PropertyName>Drives</PropertyName> </ListItem> <ListItem> <PropertyName>Path</PropertyName> </ListItem> <ListItem> <PropertyName>Home</PropertyName> </ListItem> <ListItem> <PropertyName>Description</PropertyName> </ListItem> <ListItem> <PropertyName>Capabilities</PropertyName> </ListItem> <ListItem> <PropertyName>ImplementingType</PropertyName> </ListItem> <ListItem> <PropertyName>AssemblyInfo</PropertyName> </ListItem> </ListItems> </ListEntry> </ListEntries> </ListControl> </View>
Now, looking at an object of type System.Management.Automation.ProviderInfo using Get-Member we can see all of the Properties that are available on the object:
PS C:\> (resolve-path HKLM:\SOFTWARE\Microsoft).provider | get-member
TypeName: System.Management.Automation.ProviderInfo
Name MemberType Definition ---- ---------- ---------- Equals Method System.Boolean Equals(Object obj) GetHashCode Method System.Int32 GetHashCode() GetType Method System.Type GetType() get_Capabilities Method System.Management.Automation.Provider.ProviderCapabilities get_Capabilities() get_Description Method System.String get_Description() get_Drives Method System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSDriveInf... get_HelpFile Method System.String get_HelpFile() get_Home Method System.String get_Home() get_ImplementingType Method System.Type get_ImplementingType() get_Name Method System.String get_Name() get_PSSnapIn Method System.Management.Automation.PSSnapInInfo get_PSSnapIn() set_Description Method System.Void set_Description(String value) set_Home Method System.Void set_Home(String value) ToString Method System.String ToString() Capabilities Property System.Management.Automation.Provider.ProviderCapabilities Capabilities {get;} Description Property System.String Description {get;set;} Drives Property System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSDriveInf... HelpFile Property System.String HelpFile {get;} Home Property System.String Home {get;set;} ImplementingType Property System.Type ImplementingType {get;} Name Property System.String Name {get;} PSSnapIn Property System.Management.Automation.PSSnapInInfo PSSnapIn {get;}
As we can see, AssemblyInfo is actually not a property of the object.
So, what is happening here is:
(resolve-path HKLM:\SOFTWARE\Microsoft).provider | format-list *
Piping the output to format-list * assumes inherent usage of the properties switch, and, just as with Get-Member enumerating properties, format-list is reading all of the properties from the object returning a nice list of their names and values - correct behavior.
(resolve-path HKLM:\SOFTWARE\Microsoft).provider | format-list
Piping the output to format-list (no properties or properties wildcard) invokes the default View definition in PowerShellCore.format.ps1xml, which enumerates what is defined and the values of those Properties, formatting them in a list.
This, again, is the correct behavior, it's just that the default View contains the property AssemblyInfo that is not present on this instance of this System.Management.Automation.ProviderInfo object. Therefore, we see the property showing in the result, of course with no value as the property is not really there. This is exactly how the .Net Type system is supposed to work, just the same as if you typed “format-list name,assemblyinfo”, which would show the empty looking property.
Thanks Rich for your response!
Like my other PowerShell blog posts, this is a sample of what a script can do. This post is really meant for my PFE co-workers. I posted recently about using Outlook to simply list out calendar appointments with a basic filter. I finally had some time today during travel to Chicago to work on the script I was beginning to play with when I wrote the other post. This script looks in my default calendar for appointments that I use to keep track of customer visits. It also looks for special appointments that this script creates to keep it from creating duplicates if its run more than once.
The script finds each one of these "dispatch" by their name and only looks at the last 6 days. I create a hash table ( @{} ) to store a quick list of the existing appointments that past runs of this script has already created. I populate it by looping through the list of found auto-appointments. Once I have all that, I loop through the dispatch appointments looking to see if the hash table has an entry for the appointment we want to ensure either already exists or we need to create. If its not found, we go ahead and create a new appointment item for 6 days in the future to remind me of a deadline associated with each onsite visit.
I don't have a lot of bells and whistles here. Its a basic concept script that does work on my machine. I plan to take this start and make it into a more production level script to run as a regular task for myself, basically each time I get a new "dispatch" on my calendar. I suspect some of my PFE buds will like this as well.
I changed the prefixes in the sample script to obscure the info we actually have in our calendars, so PFE folks reading this, just ping me and I can physically send you this script with the proper strings in there for this work right.
$Outlookapp = new-object -comobject outlook.application $allCalItems = $Outlookapp.GetNamespace("MAPI").GetDefaultFolder(9).items $dispatches = $allCalItems | where-object{$_.subject -like "Prefix*" -and $_.end -gt (Get-Date).Adddays(-7)} $ReportDueItems = $allCalItems | where-object{$_.subject -like "NewItemPrefix*"} #$reportdueitems | Format-Table subject $ExistingTripReportDueItems = @{} foreach ($ReportDueItem in $reportdueitems) { If ($ExistingTripReportDueItems[$reportdueitem.subject] -eq $null) {$ExistingTripReportDueItems[$reportdueitem.subject] = $reportdueitem.end} } foreach ($dispatch in $dispatches) { If ($ExistingTripReportDueItems["NewItemPrefix - " + $dispatch.subject] -eq $null) { $newitem = $outlookapp.createItem(1) $newitem.subject = [string]("NewItemPrefix - " + $dispatch.subject) $newitem.start = ([datetime]($dispatch.end)).AddDays(6) $newitem.Alldayevent = $true $newitem.Save() "Just Created new calendar item: NewItemPrefix - " + $dispatch.subject } Else { Write-Host -ForegroundColor Yellow "Item Already Present: " $dispatch.subject } }
$Outlookapp = new-object -comobject outlook.application
$allCalItems = $Outlookapp.GetNamespace("MAPI").GetDefaultFolder(9).items
$dispatches = $allCalItems | where-object{$_.subject -like "Prefix*" -and $_.end -gt (Get-Date).Adddays(-7)}
$ReportDueItems = $allCalItems | where-object{$_.subject -like "NewItemPrefix*"}
#$reportdueitems | Format-Table subject
$ExistingTripReportDueItems = @{}
foreach ($ReportDueItem in $reportdueitems)
If ($ExistingTripReportDueItems[$reportdueitem.subject] -eq $null) {$ExistingTripReportDueItems[$reportdueitem.subject] = $reportdueitem.end}
foreach ($dispatch in $dispatches)
If ($ExistingTripReportDueItems["NewItemPrefix - " + $dispatch.subject] -eq $null)
$newitem = $outlookapp.createItem(1)
$newitem.subject = [string]("NewItemPrefix - " + $dispatch.subject)
$newitem.start = ([datetime]($dispatch.end)).AddDays(6)
$newitem.Alldayevent = $true
$newitem.Save()
"Just Created new calendar item: NewItemPrefix - " + $dispatch.subject
Write-Host -ForegroundColor Yellow "Item Already Present: " $dispatch.subject
During the brief testing I had to do to debug this concept code, I needed a way to delete all the test appointments I had created (I had many duplicates at first). I wrote a simple little script that will wipe your calendar of any of these auto-appointments.
#Clean up Auto Appointment Items $Outlookapp = new-object -comobject outlook.application $allCalItems = $Outlookapp.GetNamespace("MAPI").GetDefaultFolder(9).items $dispatches = $allCalItems | where-object{$_.subject -like "NewItemPrefix*"} $dispatches | foreach-object {$_.Delete()}
#Clean up Auto Appointment Items
$dispatches = $allCalItems | where-object{$_.subject -like "NewItemPrefix*"}
$dispatches | foreach-object {$_.Delete()}
I hope this script ends up being helpful for PFE buds out there, and maybe will show some simple use of the Outlook.application object model.
So this post is born out of my playing with writing a script to help me be a better PFE. I need to automatically create calendar appoints to remind me to get all my PFE “paperwork” in on time. my colleagues will know what I am talking about when I say my TRs. My thought was to simply use the Outlook.Application Com object to search for certain calendar appointments whose subject begins with a certain word. I have just started playing with the code for what will later become the large script, but its funny…I have forgotten just how easily you can work with your Outlook data.
Take this one-liner, yes you believe this can be done in one line…wow:
(new-object -comobject outlook.application).GetNamespace("MAPI").GetDefaultFolder(9).items | where-object {$_.end -gt (get-date) -and $_.subject -like "AXIS*"} | format-table subject,end
This creates a simple table listing all calendar appoints in my main default calendar that end later than right now and that have a subject that starts with a word, in this case “Axis”. It is just so simple.
One of these days I will take the time to actually write the script I am thinking of where it starts with this list and then creates complementary appointments for these. I will share that when I write it, I just felt like I wanted to share just how simple it can be to work with Outlook data.
I was delivering my session today at the Alpharetta (Atlanta) PFE Technology Day, and needed to come up with a good way to give away a free book to the class. Right before class I threw a little script together to use the random number capabilities of PowerShell. The script is below:
$rand = new-object random
$ofstudents = 50
for ($i=1;$i -le 100;$i++)
{[math]::Round($rand.NextDouble()*$ofstudents,0)}
start-sleep -seconds 2
cls
write-host "And the Winner Is!!!!!"
start-sleep -seconds 5
(new-object -comobject wscript.shell).Popup($([math]::Round($rand.NextDouble()*$ofstudents,0)))
The key piece here is the use of the object type "random" which is system.random and can be found in the MSDN .Net documentation (http://msdn.microsoft.com/en-us/library/system.random.aspx). Once the object is created, you use methods to actually get the numbers. I have always like getting a decimal between 0 and 1 so that I can simply multiply it times the range I want to generate any number. I am using the .NextDouble() method to get my numbers (http://msdn.microsoft.com/en-us/library/system.random.nextdouble.aspx). In this case I multiply it times the number of students and round off the number. I do this 100 times and the 101 first random number is the winner. I took this opportunity as well to use the old wscript.shell .Popup() method to created a little popup box for the winner display. Like most of my posts, this is not rocket science here, but it was a fun little way to demo a simple PowerShell script that uses random numbers and an old COM object while giving away some SWAG...yes it was Ed Wilson's PowerShell Step-By-Step book :) This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.
The key piece here is the use of the object type "random" which is system.random and can be found in the MSDN .Net documentation (http://msdn.microsoft.com/en-us/library/system.random.aspx). Once the object is created, you use methods to actually get the numbers. I have always like getting a decimal between 0 and 1 so that I can simply multiply it times the range I want to generate any number. I am using the .NextDouble() method to get my numbers (http://msdn.microsoft.com/en-us/library/system.random.nextdouble.aspx). In this case I multiply it times the number of students and round off the number. I do this 100 times and the 101 first random number is the winner.
I took this opportunity as well to use the old wscript.shell .Popup() method to created a little popup box for the winner display. Like most of my posts, this is not rocket science here, but it was a fun little way to demo a simple PowerShell script that uses random numbers and an old COM object while giving away some SWAG...yes it was Ed Wilson's PowerShell Step-By-Step book :)
I thought I would share a few simple functions that I find handy. They are nowhere near rocket science but still worth it. I think they would make a nice addition to your PowerShell profile so they are available each time you load PowerShell.
function Get-IP {ipconfig | where-object {$_ –like “*IPv4 Address*”}} function cd\ {cd \} function cd.. {cd ..} function To-Binary{param($Num = $(Throw "We need a number to operate!"));[convert]::ToString($Num,2)} function From-Binary{param([string]$Num = $(Throw “We need a number to operate!”));If ($Num.length -le 31) {[convert]::ToInt32($Num,2)} ElseIf ($Num.Length –le 63) {[convert]::ToInt64($Num,2)} Else {“Number too large for this function”}}
I will post again soon with a little detail about these. They are not fully featured so there would be many ways to break these of course. Take from them what you will. One last warning, with any functions/alias/scripts, don't use them you are writing scripts or sharing PowerShell with others. If you do use these, please ensure you include them with the code you are sharing.