Skip to main content

9 posts tagged with "powershell"

View All Tags

Introducing pstools.queue

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

Recently I developed a tool that utilizes the .NET class system.collections.queue. The function runs continously and produces workitems that should be processed in sequence and in the same order they were added. One simple way to acheive this is to use the collection queue. This class provides a simple way to add items to the queue and retreive the oldest item when needed.

For fun I wanted to create a wrapper for the queue class so that it can be used in a powershell style syntax. Additionally i missed functionality to see the rate and velocity that items were added and removed to the queue.

Queue objects created with this module contains metrics for count of added and removed items, rate of added and removed items and velocity.

Repo
Docs
PowerShell Gallery

Pester, mock remote or unavailable cmdlets

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

There are a lot of forum post of people having a hard time mocking some cmdlets, either it is remote cmdlets imported by a session that is not available during testing, cmdlets that are not present on the development machine or simply some built in cmdlets that are hard to mock like Import-PSSession, Invoke-Command etc.

Mocking a cmdlet that are unavailable on the development machine is quite straight forward by just declaring a dummy function before the mock.

function Get-Foo {
$Mailboxes = Get-Mailbox
}
Describe -Name "Foo" -Fixture {
BeforeAll {
function Get-Mailbox {}
Mock Get-Mailbox -MockWith {
# return fake mailbox object
}
}
It -Name "Bar" -Test {
{ Get-Foo } | should -not -throw
}
}

The same method can be applied when mocking a built in cmdlet that will fail parameter validation which happends even though the cmdlets is mocked

function Get-Foo {
$Session = New-PSSession
Import-PSSession -Session $Session
}
Describe -Name "Foo" -Fixture {
BeforeAll {
Mock New-PSSession -MockWith {}
Mock Import-PSSession -MockWith {}
}
It -Name "Bar" -Test {
{ Get-Foo } | should -not -throw
}
}
[-] Foo.Bar 51ms (49ms|1ms)
Expected no exception to be thrown, but an exception "Cannot validate argument on parameter 'Session'. The argument is null. Provide a valid value for the argument, and then try running the command again." was thrown from C:\Users\hanpalmq\OneDrive - Crayon Group\Desktop\temp.ps1:3 char:31
+ Import-PSSession -Session $Session
+ ~~~~~~~~.
at { Get-Foo } | should -not -throw, C:\Users\hanpalmq\OneDrive - Crayon Group\Desktop\temp.ps1:12
at <ScriptBlock>, C:\Users\hanpalmq\OneDrive - Crayon Group\Desktop\temp.ps1:12
Tests completed in 214ms
Tests Passed: 0, Failed: 1, Skipped: 0 NotRun: 0

The error states that Import-PSSession requires valid PSSession object to be passed to the session parameter. Again we need to define a dummy function that don't expects a PSSession object to be passed.

function Get-Foo {
$Session = New-PSSession
Import-PSSession -Session $Session
}
Describe -Name "Foo" -Fixture {
BeforeAll {
function Import-PSSession {}
Mock New-PSSession -MockWith {}
Mock Import-PSSession -MockWith {}
}
It -Name "Bar" -Test {
{ Get-Foo } | should -not -throw
}
}

Convert-DateStringToDateTimeObject

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

Eventually you will stumble upon badly formatted date/time strings that you need to parse. Instead of doing a lot of Split, Trim, Substring and what not you can leverage the DateTime class methods Parse/TryParse/ParseExact. To PS-ify the use of this method I wrote this powershell function that can take any date/time string and convert it to either a DateTime object or a new string format.

function Convert-DateStringToDateTimeObject {
<#
.DESCRIPTION
Parses a datetimestring with a defined pattern to a datetime object
.PARAMETER DateString
Defines the string to parse
.PARAMETER PatternIn
Defines the pattern that datestring is formatted in
.PARAMETER PatternOut
Optional. If this parameter is omitted a standard datetime object is
returned. It is however possible to define an output pattern where
the datetime object is converted back to a string but with the output
pattern instead.
.PARAMETER Culture
Defines to culture to use for conversion. Default is console default ($PSCulture)
.EXAMPLE
$InputString = '2018_06_11_11_05_03'
Convert-DateStringToDateTimeObject -DateString $InputString -PatternIn
'yyyy_MM_dd_HH_mm_ss' -PatternOut 'yyyy-MM-dd HH:mm:ss'
Convert the string date time representation '2018_06_11_11_05_03' to a
valid datetime object and formats that datetime object to a new string format.
.NOTES
AUTHOR Hannes Palmquist
AUTHOREMAIL hannes.palmquist@outlook.com
COPYRIGHT © 2019, Hannes Palmquist, All Rights Reserved
#>
param(
[Parameter(Mandatory)][string]$DateString,
[Parameter(Mandatory)][string]$PatternIn,
[string]$PatternOut = '',
[string]$Culture = $PSCulture
)
$DateTimeFormat = [cultureinfo]::GetCultureInfo($Culture).DateTimeFormat
$DateTimeObject = [DateTime]::ParseExact($DateString, $PatternIn, $DateTimeFormat)
if ($PatternOut -eq '') {
Write-Output $DateTimeObject
} else {
Write-Output $DateTimeObject.ToString($PatternOut)
}
}

So if we have a date/time string like “2018_06_11_11_05_03” we can convert that date time to a date time object by writing:

Convert-DateTimeStringToDateTimeObject -InputString "2018_06_11_11_05_03" -PatternIn "yyyy_MM_dd_HH_mm_ss"
11 juni 2018 11:05:03

You can also use the parameter “PatternOut” to set a specific format to return the DateTime object as.

Note that “\” (backslash) and “:” (semi-colon) needs to be escaped.

Get-WeekInfo

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

In some parts of the world it is more common to work with weeks as measurement of time. Unfortunately there are not easy accessible ways to work with weeks in powershell or .NET. There is some support of retrieving a week number with the culture datatype however if you have a week number and want to resolve dates relatated to that week number you have to resolve that manually. Here is one example of how to do it.

First we define a supporting function that simply retreives the week number for a given date from the Gregorian calendar.

function Get-CalendarWeek {
<#
.DESCRIPTION
Gets the current week number based on a specific culture and it's week number descision rules.
.PARAMETER Date
Defines the date at which to return the week number for. Defaults to the current date.
.PARAMETER CultureInfo
Defines the culture that should be used to calculate the week number. Defaults to se-SV.
.EXAMPLE
Get-CalendarWeek
Get the week number for the current date.
.EXAMPLE
Get-CalendarWeek -Date 2018-01-25 -CultureInfo en-US
Get the week number for the date 2018-01-25 according to the week number calculation rules of the en-US culture.
.NOTES
Author: Hannes Palmquist
AuthorEmail: hannes.palmquist@outlook.com
COPYRIGHT: © 2019, Hannes Palmquist, All Rights Reserved
#>
param(
[datetime]$Date = (Get-Date),
[string]$CultureInfo = $PSCulture
)
# Get specific culture object
$Culture = [cultureinfo]::GetCultureInfo($CultureInfo)
# retrieve calendar week
write-output $Culture.Calendar.GetWeekOfYear($Date, $Culture.DateTimeFormat.CalendarWeekRule, $Culture.DateTimeFormat.FirstDayOfWeek)
}

When we have that function we can define the function that can resolve the week.

function Get-WeekInfo {
<#
.DESCRIPTION
Gets info about a specific week
.PARAMETER Week
Defines the week number to query
.PARAMETER Year
Defines which year to query
.EXAMPLE
Get-WeekInfo -Week 5 -Year 1988
Gets the first date of the fifth week of 1988
.NOTES
Author: Hannes Palmquist
AuthorEmail: hannes.palmquist@outlook.com
Copyright: (c) 2019, Hannes Palmquist, All Rights Reserved
#>
[CmdletBinding()] # Enabled advanced function support
param(
[Parameter(Mandatory)][ValidateRange(1, 53)][int]$Week,
[Parameter(Mandatory)][ValidateRange(1600, 2100)][int]$Year
)
BEGIN {
$WeekHash = [ordered]@{
Week = $Week
Year = $Year
}
}
PROCESS {
$ReferenceDate = Get-Date -Year $Year -Month 02 -Date 05
$ReferenceWeek = Get-CalendarWeek -Date $ReferenceDate
$WeeksDiff = $Week - $ReferenceWeek
$DateInWeek = $ReferenceDate.AddDays($WeeksDiff * 7)
$WeekHash.FirstDateOfWeek = $DateInWeek.AddDays(1 - [int]$DateInWeek.DayOfWeek)
$WeekHash.LastDateOfWeek = $WeekHash.FirstDateOfWeek.AddDays(7).AddMilliseconds(-1)
$WeekHash.StartsInMonth = ([cultureinfo]::GetCultureInfo($PSCulture)).DateTimeFormat.MonthNames[($WeekHash.FirstDateOfWeek).Month-1]
$WeekHash.EndsInMonth = ([cultureinfo]::GetCultureInfo($PSCulture)).DateTimeFormat.MonthNames[($WeekHash.LastDateOfWeek).Month-1]
}
END {
Write-Output ([pscustomobject]$WeekHash)
}
}

This will allow us to perform queries like:

Get-WeekInfo -Week 30 -Year 2018
Week : 30
Year : 2018
FirstDateOfWeek : 2018-07-23 00:00:00
LastDateOfWeek : 2018-07-29 23:59:59
StartsInMonth : July
EndsInMonth : July

Change desktop wallpaper with powershell

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

I’ve seen numerous forums and blog articles trying to to change desktop wallpaper in windows, none of which works reliably. The most common solution is to set a new registry keys and then call user32.dll and the method UpdatePerUserSystemParameters and then quite literally hope that the desktop wallpaper changes. This is not always the case because Windows does not always honor the request to actually update the wallpaper settings when this method is called. The inner working of this method is not completely known and this method has never been advertised by Microsoft to be the way to change wallpaper.

However I came to the conclusion that it must exist a documented windows API to actually set a new wallpaper so I started looking into C# solutions to the same problem and sure thing it was a quite an easy procedure to change the desktop wallpaper. All I had to do was to define the type definition in Powershell and then pass the action values when calling the SystemParametersInfo method.

The below Powershell function will reliably change the desktop wallpaper and you also have the possibility to choose the style.

<#PSScriptInfo
.VERSION 1.0.0.0
.GUID cfc2e719-67d8-4722-b594-3d198a1206c7
.FILENAME Set-DesktopWallpaper.ps1
.AUTHOR Hannes Palmquist
.AUTHOREMAIL hannes.palmquist@outlook.com
.CREATEDDATE 2019-10-14
.COMPANYNAME Personal
.COPYRIGHT (c) 2019, Hannes Palmquist, All Rights Reserved
#>
function Set-DesktopWallpaper {
<#
.DESCRIPTION
Sets a desktop background image
.PARAMETER PicturePath
Defines the path to the picture to use for background
.PARAMETER Style
Defines the style of the wallpaper. Valid values are, Tiled, Centered, Stretched, Fill, Fit, Span
.EXAMPLE
Set-DesktopWallpaper -PicturePath "C:\pictures\picture1.jpg" -Style Fill
.EXAMPLE
Set-DesktopWallpaper -PicturePath "C:\pictures\picture2.png" -Style Centered
.NOTES
Author: Hannes Palmquist
AuthorEmail: hannes.palmquist@outlook.com
COPYRIGHT: © 2019, Hannes Palmquist, All Rights Reserved
Supports jpg, png and bmp files.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$PicturePath,
[ValidateSet('Tiled', 'Centered', 'Stretched', 'Fill', 'Fit', 'Span')]$Style = 'Fill'
)
BEGIN {
$Definition = @"
[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
"@
Add-Type -MemberDefinition $Definition -Name Win32SystemParametersInfo -Namespace Win32Functions
$Action_SetDeskWallpaper = [int]20
$Action_UpdateIniFile = [int]0x01
$Action_SendWinIniChangeEvent = [int]0x02
$HT_WallPaperStyle = @{
'Tiles' = 0
'Centered' = 0
'Stretched' = 2
'Fill' = 10
'Fit' = 6
'Span' = 22
}
$HT_TileWallPaper = @{
'Tiles' = 1
'Centered' = 0
'Stretched' = 0
'Fill' = 0
'Fit' = 0
'Span' = 0
}
}
PROCESS {
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name wallpaperstyle -Value $HT_WallPaperStyle[$Style]
Set-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name wallpaperstyle -Value $HT_TileWallPaper[$Style]
$null = [Win32Functions.Win32SystemParametersInfo]::SystemParametersInfo($Action_SetDeskWallpaper, 0, $PicturePath, ($Action_UpdateIniFile -bor $Action_SendWinIniChangeEvent))
}
END {
}
}

Exchange Versions

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud
Exchange Versions

I’ve summarized a table with all versions released of Exchange Server that shows version numbers, release date, .net requirement, OS requirement, forest functional level etc in a simple to use excel spreadsheet.

I’ll try to keep the file updated as often as I can, but if you got data you want to add please send me a message.

Download this excel file

How to verify group membership

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

This function can be used to show the status of the Powershell profile scripts on the computer.

function Check-ProfileStatus {
($profile | Get-Member -MemberType NoteProperty).Name |
ForEach-Object {
$CurrentProfile = $_
$path = $profile.$_
[pscustomobject]([Ordered]@{Profile=$CurrentProfile;Path=$Path;Exists=(Test-Path $Path)})
}
}
Check-ProfileStatus

Check-ProfileStatus

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

This function can be used to show the status of the Powershell profile scripts on the computer.

function Check-ProfileStatus {
($profile | Get-Member -MemberType NoteProperty).Name |
ForEach-Object {
$CurrentProfile = $_
$path = $profile.$_
[pscustomobject]([Ordered]@{Profile=$CurrentProfile;Path=$Path;Exists=(Test-Path $Path)})
}
}
Check-ProfileStatus

Where-Object is slow...

Hannes Palmquist

Hannes Palmquist

Senior Consultant Cloud

One thing that I have been struggling with from time to time is that the cmdlet Where-Object is incredibly slow to filter massive datasets. Lets say you have custom PSObject array with 50000 objects and 20 properties each. If you would cross referencing this table with another large dataset using the Where-Object cmdlet for each lookup it would take ages.

One day I had to do such a comparison and I was forced to come up with an alternate way of retrieving matches, so I developed a new function that is much faster than the Where-Object cmdlet.

Lets say you have a CSV-file containing 50000 rows and 20 columns with one column being a QUID. First you need to create an index:

$CSVIndex = $CSV.GUID.ToLower()

Once that is done the search can be started using the cmdlet below:

Fast-Search -Database $CSV -DatabaseIndex $CSVIndex -SearchString "A52FB-...-27422"

This is how the function is defined

function Fast-Search {
param(
$Database,
$DatabaseIndex,
$SearchString
)
$Array = @()
$Index = 0
while ($Index -ne -1) {
$Index = system.array]::IndexOf($DatabaseIndex,$SearchString,$Index+1)
if ($Index -ne -1) {
$Array += $Index
}
}
$Array | ForEach-Object {
$Database[$_]
}
}

What makes the function so much faster you might ask..

First of, the key is that the dataset and the dataset index does not change order internally in the array as we assume that the item on Index=X is the same item both in the dataset and the dataset index.

So what we do is to search for the SearchString only in the dataset index, this in itself i much faster as it does not have to process as much data. Then we use the method IndexOf of the dataset index. This is also quite fast localizing the first row that matches the SearchString. Then we save that index in another array, lets call it the result array. When that is done we continue to search for the next match after the last result. This process is repeated until we reach the end of the dataset index.

We then have an array of indexes with the “index numbers” of the rows that match the SearchString. The Last thing we need to do is to collect the rows from the large dataset using array index targeting.

$SomeArray[$TheIndexThatWeWantToRetreive]

And last but now least, we return all the objects from the dataset.

In some cases I have had performance benefits by using this method by up to 80 times compared to using Where-Object. The drawback is that it isn’t a built-in cmdlet so you have to declare the function and also you need to build an index manually and last that you can only search in one property at a time, the index that you created. You should only use this method for the specific use cases when you have two very large datasets where the key isn’t unique. The function can also be developed further to accept two or more indexes in case you need to search for more than one property.

A similar solution is to use a hashtable as dataset index lookup table and simply store the index value as key and the whole object as the value of the key. This method is quite easy to use however it has one drawback; keys must be unique. So if you need to search a large dataset fast where you expect more than one result based on the index this function give you really fast searches.