RSS

Author Archives: Bjorn Houben

#PowerShell – Getting started with #Pester unit tests to improve code quality

#PowerShell – Getting started with #Pester unit tests to improve code quality

On my list I have a couple of PowerShell related subjects I want/need to learn more about. These include, but are not limited to parallelization, GitHub, DSC, PowerShell Core, JEA, Plaster, DBAtools and DBAchecks.

I have been playing around a bit already with parallelization and Github and the next thing on my list was Pester.

Pester in general

For those that do not know it, the Pester Wiki describes Pester as follows:
“Pester provides a framework for running Unit Tests to execute and validate PowerShell commands”.

So basically you use it to run scripts/functions/code with a specific input and tell Pester what the expected output should be. Then the result of the test either passes or fails. This way you can verify if your code works as expected.

Instead of using it to test your code, you can also use it to test if a configuration is the way it should be/you want it to be. By example if you’ve created an Operating System deployment or configured DSC, you can check if the configuration has been applied (correctly) or not.

Another advantage of having Pester tests is that you can easily re-run tests without spending much time.

Reasons to re-run these tests could be if you run the code under other circumstances like by example with PowerShell Core instead of Windows PowerShell or on Windows Server 2019 instead of Windows Server 2012 R2.  A more common reason is when you change (parts of) your code that you can easily verify if your changes did not break anything. Keep in mind however that if you change your code, you might also need to change and/or add tests.

If you want to, you can use Pester as part of your Continuous Integration and Continuous Deployment (CI/CD) pipeline. But that’s beyond my knowledge and this post. If you want to know more about it, check Microsoft’s article ‘Building a Continuous Integration and Continuous Deployment pipeline with DSC’.

My plan for getting started with Pester

First, I started reading some articles about Pester, then I purchased ‘The Pester Book’ by Adam Bertram. I started reading the first couple of chapters, but didn’t finish it yet. It did provide me with the basics I needed to get started though. Instead of continuing to read the book, I decided to simply start using Pester with a simple real-life example and then build upon that.

The example I decided to start was a simple script I wrote in the past for a colleague to translate network addresses in the IP CIDR format to the IP subnet mask format. By example if the IP CIDR address was ‘192.168.1.1/24’ the output should be ‘192.168.1.1 255.255.255.0’. Basically it would take the input ‘192.168.1.1/24’ split it at the forward slash ( / ) and then use a switch statement to translate the CIDR notation (by example 24) to the subnet mask notation (by example 255.255.255.0).

To be honest I didn’t expect many advantages of using Pester for this specific script.

Actually getting started with Pester

Actually getting started with Pester consisted of the following steps that will be described in more detail:

  1. Installing Pester
  2. Creating a Pester tests file
  3. Coming up with tests
  4. Actually writing the PowerShell / Pester code for the tests you came up with
  5. Running the tests
  6. Analyzing the results, fixing code and re-running tests until all tests pass
  7. Checking code coverage, modifying code / tests, rerunning tests and checking code coverage

I have tried to include relevant parts of the code in my blog, but readability isn’t as good as I would like it to be. So it’s best if you simply download the files yourself from my GitHub.

1. Installing Pester

In my case I had already installed the latest version of Pester, but if you haven’t done that already you can do so by running PowerShell as administrator and running:

Install-Module -Name Pester -Force -SkipPublisherCheck

2. Creating a Pester tests file

My initial script full path was C:\Scripts\Convert-IPCIDRNotationtoIPSubnetMaskNotation.ps1 and the initial version of it can be found on my GitHub page.

Then I used

New-Fixture -path C:\Scripts -Name Convert-IPCIDRNotationtoIPSubnetMaskNotation

to create a C:\Scripts\Convert-IPCIDRNotationtoIPSubnetMaskNotation.tests.ps1 file that is linked to the initial script and that will contain the Pester tests for my script. The linking looks as follows:

$here = Split-Path -Parent $MyInvocation.MyCommand.Path

$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace ‘\.Tests\.’, ‘.’

. $here\$sut

Sidenote: When using Test Driven Development (TDD) to create your code you first define your tests and then write your code. So if you create a new solution this way, you would also use New-Fixture and it would create both your empty script file and a linked Pester tests file.

3. Coming up with tests

Think about all possible scenarios and how you can test them. In my case the tests should at least test all possible input CIDR values ranging from 0 to 32 and if the subnet mask output would be what I expected.

4. Actually writing the PowerShell / Pester code for the tests you came up with

Pester tests have their own terminology and syntax. The Pester Book’ by Adam Bertram does a great job covering them in detail.

In this example in its simplest form you have  a ‘Describe‘ block containing a single test or a logical group of tests contained in ‘It‘ statements where you define the code that should be run and what the output should be. By example you could have a logical group of tests for testing input validation and you could have a logical group of tests for testing if the output is as expected. In

This would then by example look like this:

$here = Split-Path -Parent $MyInvocation.MyCommand.Path

$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace ‘\.Tests\.’, ‘.’

. $here\$sut

Describe “Verify input validation working correctly” {

    It “Valid input (1.1.1.1/0) Should not throw” {

        {Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR ‘1.1.1.1/0’ | Should Not Throw}

    }

    It “Invalid formatting (1.1.1.1-0 instead of 1.1.1.1/0) should throw” {

        {Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR ‘1.1.1.1-0’ | Should Not Throw}

    }

    It “Invalid IP address (1.1.1.1.1) Should throw” {

        {Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR ‘1.1.1.1.1/0’ | Should Not Throw}

    }

    It “Invalid IP address (1.1.1.256) Should throw” {

        {Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR ‘1.1.1.1.1/0’ | Should Not Throw}

    }

    It “Invalid IP address (001.001.001.001) Should throw” {

        {Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR ‘1.1.1.1.1/0’ | Should Not Throw}

    }

    It “Invalid CIDR (/33) Should Throw” {

        {Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR ‘1.1.1.1/33’ | Should Throw}

    }

}

Describe “Verify if output from Convert-IPCIDRNotationtoIPSubnetMaskNotation is correct” {

    $TestCases = @(

        @{InputValue = ‘192.168.1.1/0’; ExpectedIPCIDR = ‘192.168.1.1/0’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘0.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 0.0.0.0’}

        @{InputValue = ‘192.168.1.1/1’; ExpectedIPCIDR = ‘192.168.1.1/1’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘128.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 128.0.0.0’}

        @{InputValue = ‘192.168.1.1/2’; ExpectedIPCIDR = ‘192.168.1.1/2’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘192.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 192.0.0.0’}

        @{InputValue = ‘192.168.1.1/3’; ExpectedIPCIDR = ‘192.168.1.1/3’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘224.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 224.0.0.0’}

        @{InputValue = ‘192.168.1.1/4’; ExpectedIPCIDR = ‘192.168.1.1/4’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘240.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 240.0.0.0’}

        @{InputValue = ‘192.168.1.1/5’; ExpectedIPCIDR = ‘192.168.1.1/5’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘248.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 248.0.0.0’}

        @{InputValue = ‘192.168.1.1/6’; ExpectedIPCIDR = ‘192.168.1.1/6’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘252.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 252.0.0.0’}

        @{InputValue = ‘192.168.1.1/7’; ExpectedIPCIDR = ‘192.168.1.1/7’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘254.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 254.0.0.0’}

        @{InputValue = ‘192.168.1.1/8’; ExpectedIPCIDR = ‘192.168.1.1/8’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.0.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.0.0.0’}

        @{InputValue = ‘192.168.1.1/9’; ExpectedIPCIDR = ‘192.168.1.1/9’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.128.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.128.0.0’}

        @{InputValue = ‘192.168.1.1/10’; ExpectedIPCIDR = ‘192.168.1.1/10’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.192.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.192.0.0’}

        @{InputValue = ‘192.168.1.1/11’; ExpectedIPCIDR = ‘192.168.1.1/11’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.224.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.224.0.0’}

        @{InputValue = ‘192.168.1.1/12’; ExpectedIPCIDR = ‘192.168.1.1/12’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.240.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.240.0.0’}

        @{InputValue = ‘192.168.1.1/13’; ExpectedIPCIDR = ‘192.168.1.1/13’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.248.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.248.0.0’}

        @{InputValue = ‘192.168.1.1/14’; ExpectedIPCIDR = ‘192.168.1.1/14’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.252.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.252.0.0’}

        @{InputValue = ‘192.168.1.1/15’; ExpectedIPCIDR = ‘192.168.1.1/15’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.254.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.254.0.0’}

        @{InputValue = ‘192.168.1.1/16’; ExpectedIPCIDR = ‘192.168.1.1/16’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.0.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.0.0’}

        @{InputValue = ‘192.168.1.1/17’; ExpectedIPCIDR = ‘192.168.1.1/17’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.128.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.128.0’}

        @{InputValue = ‘192.168.1.1/18’; ExpectedIPCIDR = ‘192.168.1.1/18’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.192.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.192.0’}

        @{InputValue = ‘192.168.1.1/19’; ExpectedIPCIDR = ‘192.168.1.1/19’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.224.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.224.0’}

        @{InputValue = ‘192.168.1.1/20’; ExpectedIPCIDR = ‘192.168.1.1/20’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.240.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.240.0’}

        @{InputValue = ‘192.168.1.1/21’; ExpectedIPCIDR = ‘192.168.1.1/21’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.248.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.248.0’}

        @{InputValue = ‘192.168.1.1/22’; ExpectedIPCIDR = ‘192.168.1.1/22’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.252.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.252.0’}

        @{InputValue = ‘192.168.1.1/23’; ExpectedIPCIDR = ‘192.168.1.1/23’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.254.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.254.0’}

        @{InputValue = ‘192.168.1.1/24’; ExpectedIPCIDR = ‘192.168.1.1/24’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.0’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.0’}

        @{InputValue = ‘192.168.1.1/25’; ExpectedIPCIDR = ‘192.168.1.1/25’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.128’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.128’}

        @{InputValue = ‘192.168.1.1/26’; ExpectedIPCIDR = ‘192.168.1.1/26’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.192’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.192’}

        @{InputValue = ‘192.168.1.1/27’; ExpectedIPCIDR = ‘192.168.1.1/27’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.224’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.224’}

        @{InputValue = ‘192.168.1.1/28’; ExpectedIPCIDR = ‘192.168.1.1/28’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.240’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.240’}

        @{InputValue = ‘192.168.1.1/29’; ExpectedIPCIDR = ‘192.168.1.1/29’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.248’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.248’}

        @{InputValue = ‘192.168.1.1/30’; ExpectedIPCIDR = ‘192.168.1.1/30’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.252’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.252’}

        @{InputValue = ‘192.168.1.1/31’; ExpectedIPCIDR = ‘192.168.1.1/31’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.254’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.254’}

        @{InputValue = ‘192.168.1.1/32’; ExpectedIPCIDR = ‘192.168.1.1/32’; ExpectedIpAddress = ‘192.168.1.1’; ExpectedSubnetMask = ‘255.255.255.255’; ExpectedIpAndSubnetMask = ‘192.168.1.1 255.255.255.255’}

        )

    It “IPCIDR result for input <inputvalue> should be <ExpectedIPCIDR>” -TestCases $TestCases {

        param($InputValue, $ExpectedIpCIDR, $ExpectedIpAddress, $ExpectedSubnetMask, $ExpectedIpAndSubnetMask)

        (Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR $InputValue).IPCIDR | Should -Be $ExpectedIpCIDR

    }

    It “IPAddress result for input <inputvalue> should be <ExpectedIpAddress>” -TestCases $TestCases {

        param($InputValue, $ExpectedIpCIDR, $ExpectedIpAddress, $ExpectedSubnetMask, $ExpectedIpAndSubnetMask)

        (Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR $InputValue).IpAddress | Should -Be $ExpectedIpAddress

    }

    It “SubNetMask result for input <inputvalue> should be <ExpectedSubnetMask>” -TestCases $TestCases {

        param($InputValue, $ExpectedIpCIDR, $ExpectedIpAddress, $ExpectedSubnetMask, $ExpectedIpAndSubnetMask)

        (Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR $InputValue).SubnetMask | Should -Be $ExpectedSubnetMask

    }

    It “Ip and subnet mask result for input <inputvalue> should be <ExpectedIpAndSubnetMask>” -TestCases $TestCases {

        param($InputValue, $ExpectedIpCIDR, $ExpectedIpAddress, $ExpectedSubnetMask, $ExpectedIpAndSubnetMask)

        (Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR $InputValue).IpAndSubnetMask | Should -Be $ExpectedIpAndSubnetMask

    }

}

Sidenote: In the example above I used testcases to define multiple inputs to test, but you could also use standard PowerShell code like getting input from a CSV and using a Foreach. This would look like this:

$Tests = Import-Csv -path $here\Convert-IPCidrNotationToIpSubnetMaskNotation.Tests.Csv” -Delimiter ‘;’

Foreach ($Test in $Tests) #Determine for your own situation if you should use a foreach or Pester test cases

{

    Describe “Convert-IPCIDRNotationtoIPSubnetMaskNotation for $($Test.Input) {

        $Result = Convert-IPCIDRNotationtoIPSubnetMaskNotation -IPCIDR $Test.Input

        It “IPCIDR result should be $($Test.ExpectedIpCIDR) {

            $Result.IPCIDR | Should -Be $($Test.ExpectedIpCIDR)

        }

        It “IPAddress result should be $($Test.ExpectedIPAddress) {

            $Result.IpAddress | Should -Be $($Test.ExpectedIPAddress)

        }

        It “SubNetMask result should be $($Test.ExpectedSubnetMask) {

            $Result.SubNetMask | Should -Be $($Test.ExpectedSubnetMask)

        }

        It “Ip and subnet mask result should be $($Test.ExpectedIpAndSubnetMask) {

            $Result.IpAndSubNetMask | Should -Be $($Test.ExpectedIpAndSubnetMask)

        }

    }

}

5. Running the tests

Even though you can simply run the C:\Scripts\Convert-IPCIDRNotationtoIPSubnetMaskNotation.tests.ps1 file directly and run the tests, you should use Invoke-Pester because it gives your more options. In this simple example however, the differences aren’t that big but with Invoke-Pester it will show you how long it took to run all tests and how many tests had the result passed, failed, skipped, pending and inconclusive. So in this case you would run:

Invoke-Pester -Script ‘C:\scripts\Convert-IPCIDRNotationtoIPSubnetMaskNotation.Tests.ps1’

6. Analyzing the results, fixing code and re-running tests until all tests pass

Before I mentioned that I didn’t expect many advantages of using Pester for this specific script, but boy was I wrong. Apparently I made typos in the switch statement and without Pester I would have probably never noticed them. It could have caused major issues If this had been used in production for by example creating firewall rules.

By fixing issues and re-running the tests I finally got rid of the errors in my code. If you are interested in what mistakes I made and what I corrected, take a look at GitHub.

7. Checking code coverage, modifying code / tests, rerunning tests and checking code coverage

The code coverage of your tests basically describes to what degree your tests cover all possible scenarios. So if I had only tested only half of the options in the switch statement, my code coverage could never be 100%. For more info, see the Pester Wiki about code coverage.

The cool thing about Pester is that if you specify the -CodeCoverage parameter with Invoke-Item, it will create a code coverage report for you. So you would run by example:

Invoke-Pester -Script ‘C:\scripts\Convert-IPCIDRNotationtoIPSubnetMaskNotation.Tests.ps1’ -CodeCoverage ‘C:\scripts\Convert-IPCIDRNotationtoIPSubnetMaskNotation.ps1’

Then it would tell you what percentage is covered by your tests and it even tells you what part(s) you not have tested. I have to admit that I don’t know exactly how it works and if it’s able to detect everything so you have to be critical yourself as well.

In my case I had defined the default keyword in the switch statement to return an error if the CIDR value was not between 0 and 32. I however forgot to write a test for it. Instead of writing a test for it, I decided in this case it would be better to modify the existing script to do input validation and then write tests to test if the input validation was working correctly. After this, the code coverage was 100%

Sidenote: You don’t always have to have 100% code coverage for a script from the start. Often you have limited time and it is not feasible to write tests for scenarios that are unlikely to ever happen. This is something you have to decide yourself. You can always add/improve tests later on and each test you have created is better than having none at all.

Closing thoughts

Pester is a great tool that helps to improve your code, even for simple code. We are all human and we all make mistakes. Pester helps me finding these issues and therefore improves my code quality. It also gives me a better feeling and more confidence about the scripts I’ve written.

What I also really like, is that making changes to an existing scripts takes less time and is less likely to create issues that will not be noticed. This is because you can easily re-run the tests that you have already created. You should still keep in mind that changes to your code might require changes to existing tests or might require you to add tests. But using the -codecoverage parameter of Invoke-Pester also helps you with that.

I purposely chose this simple example just to get started, but Pester can do much more. I plan to read more about it in ‘The Pester Book’ by Adam Bertram and when I get a good use case I will blog about it.

If you have any thoughts about this article, please leave a reply as this motivates me to create more blog posts.

Advertisements
 
Leave a comment

Posted by on June 15, 2018 in ICT, Microsoft, Powershell

 

Tags: , , , , , , , , , , , , , , , ,

#PowerShell – Anybox module to easily create GUIs by @dm3ll3n

#PowerShell – Anybox module to easily create GUIs by @dm3ll3n

In my previous post #PowerShell – Create a simple GUI form in seconds I wrote about a script I made to easily create a GUI.

After posting it, a colleague of mine said he had seen a great solution for creating GUIs on Reddit called Anybox.

Anybox is created by Donald Mellenbruch (@dm3ll3n) and is available from the PowerShell Gallery. Unfortunately however there is no proper built-in help yet. He does have a great post on his website though with example GUIs and the code needed to create them:
https://www.donaldmellenbruch.com/post/introducing-the-anybox/

Be sure to check out his website and thank him if you like it.

 

Tags: , , ,

#PowerShell – Trying to have my blog added to Planet PowerShell

#PowerShell – Trying to have my blog added to Planet PowerShell

When I first saw the announcement of Planet PowerShell I loved the concept of having blog posts aggregated in a single place  to thought it was a great idea to aggregate PowerShell related blog posts.

I wanted to have my blog added to it as well. But at the time when I saw the requirements, I saw that I needed to do some stuff with GitHub and that was beyond my comfort zone back then.

Last week however I started learning and using GitHub in combination with Visual Studio Code. Then in my Twitter feed I saw the post “A Graphical Example of the Value of Planet PowerShell” by Josh King and I realized that this was something I still wanted to do.

I hope that they will add me so that it will also reach a bigger audience and other people can benefit from my blog posts. I also hope that I can benefit from more people in the community giving me pointers and advice to improve. I look forward to seeing the results 🙂

Thanks a lot to all people that make Planet PowerShell possible and also those who contribute to it with their blog posts. Without you I wouldn’t have been where I am now.

 
1 Comment

Posted by on June 2, 2018 in Powershell

 

Tags: , , , , ,

#PowerShell – Create a simple GUI form in seconds

As a PowerShell person I’m personally not a big fan of creating a GUI for PowerShell scripts. This is however something that is asked a lot when PowerShell scripts are created for by example provisioning new users.

I’ve seen many different approaches to this. Most worked around it either by using Read-Host or creating a GUI inside their function. In some cases these approaches also meant that the function could not handle pipeline input, multiple inputs, etc. So basically they automated one thing, but prevented further automation by implementing the GUI in a stupid way.

Personally I prefer to keep my functions without a GUI. If it is desired or required, the GUI code will be written outside of the function and will result in the calling of the function with the values specified in the GUI. This way the GUI part will not pollute the function and users have more choice and flexibility to achieve their goal. You could compare it to how you normally handle output. You output objects and let users handle the formatting instead of providing them with by example the output in Format-Table.

Now the approach is clear, the GUI still needs to be created. There are multiple ways to go about this. You could by example create the GUI by looking up what exactly you need to code to get the desired result or you could use (paid) 3rd party tools / services. You could by example use the free web-based service POSHGUI to visually create your form and then have it output your needed code. There are however also (paid) editors like Visual Studio or PowerShell Studio. Another very simple method in some cases is using Out-Gridview with -PassThru. For more info, see also my previous blog post Summary of 2nd Dutch PowerShell User Group – DuPSUG meeting with additional resources

In my case I created my initial GUI form with POSHGUI. Even though you can do many more advanced GUI forms, I soon realized that I was always creating the same type of GUI form that consisted of a couple of text boxes and a submit button. Creating these manually become an annoyance very quickly. That’s why I decided to create a function to dynamically create such a GUI based on my input.

The result is this:

The code itself can be found on my GitHub: https://github.com/bjornhouben/PowerShell/blob/master/General/GUI/New-SimpleGUIForm.ps1

I hope you like it and find it useful.

 
3 Comments

Posted by on June 1, 2018 in Automation, ICT, Microsoft, Powershell

 

Tags: , , , , , ,

PowerShell – Getting started with Visual Studio Code and GitHub

In the past years I’ve been creating scripts and storing them on my personal OneDrive that is connected to multiple desktops and laptops I use. Additionally, my Synology NAS syncs with OneDrive and backups the files to ChrashPlan.

Even though this works, it has some disadvantages:

  1. OneDrive provides basic version history, but it is limited to just the file at a point in time. When modifying scripts I would need to create a new version and file to ensure I would be able to go back to a previously working version. In the past, especially when I just got started with PowerShell I screwed up code that was working before and that cost me a lot of time to get working again.
  2. The file is only available to me and the people I explicitly share it with. Therefore a lot of general use scripts I’ve created will not be findable and usable by others and I cannot benefit from suggestions/improvements from others which would also help me create better code.

These are the things I plan to solve by using GitHub with Visual Studio Code.

These are the steps I did to get started:

  1. First thing I did, was install Visual Studio Code 1.23.1 (even though I love PowerShell ISE especially with ISE Steroids and will probably still use in parallel). Then I configured it as a replacement for the PowerShell ISE as described by Mike F Robbins in his blog post “How to install Visual Studio Code and configure it as a replacement for the PowerShell ISE”.
  2. Then I created an account on GitHub and followed the Hello World guide to get better acquainted with GitHub and its concepts. At the bottom of this page additional training was offered as a tip: GuidesYouTube Channel and On-Demand Training , but I decided to check this out later.
  3. I then configured Git for Windows and Visual Studio Code by following the blog post Use Git with Visual Studio Code by Brandon Lee.

Everything worked correctly from home and I started following examples shown in “Git Version Control in VS Code”.

When I got at work it did not work anymore however and after some time I got an error in Visual Studio Code stating:
Git: fatal: unable to access ‘https://github.com/bjornhouben/PowerShell.git/&#8217;: Failed to connect to github.com port 443: Timed out

Since we use a proxy at work, I quickly figured this would be the most probable cause. So first I determined the proxy server by checking our WPAD file using http://wpad.domain.local/wpad.dat. Then I went on to determine how to configure Visual Studio Code to use the proxy server and came across the articles “Network Connections in Visual Studio Code” and “User and Workspace Settings” , but both did not solve my problem even after restarting Visual Studio Code. Then it hit me that Git For Windows was being used to connect to GitHub. I then quickly found out that I had to run GIT Cmd and configure the proxy using: git config –global http.proxy http://proxyuser:proxypwd@proxy.server.com:8080 and after restarting Visual Studio Code it worked correctly again. When I’m working from home without VPN I simply unset the proxy configuration using GIT Cmd using: git config –global –unset http.proxy and restart Visual Studio Code.

 
3 Comments

Posted by on May 28, 2018 in Automation, ICT, Microsoft, Powershell

 

Tags: , , , , , , , ,

Office 365 – Create PowerShell Session is failed using OAuth when trying to connect to Exchange Online PowerShell using Connect-ExoPSSession

For some time now I’ve been using the Microsoft Exchange Online Powershell Module that supports Azure multi-factor authentication (MFA)  , but for an unknown reason it stopped working

When trying to connect to Exchange Online PowerShell using the code below with a global administrator account, I was getting an access denied error:

Connect-EXOPSSession -UserPrincipalName admin.bjorn@mydomain.nl
New-ExoPSSession : Create PowerShell Session is failed using OAuth
At C:\users\admin\adppdata\local\Apps\2.0\84VE2AT5.PKO\HHYLAA32.DC7\micr..tion_c3bce3770c238a49_0010.0000_90fa60bba125a33a\CreateExoPSSession.ps1:179 char:22
+ ... PSSession = New-ExoPSSession -UserPrincipalName $UserPrincialName -C ...
+
    + CategoryInfo          : NotSpecified: (:) [New-ExoPSSession], Exception
    + FullyqualifiedErrorID : System.Exception,Microsoft.Exchange.Management.ExoPowerShellSnapin.NewExoPSSession

For me the solution was to install the Microsoft Exchange Online Powershell Module through Programs and then reinstalling it.

For more information about the new Exchange Online PowerShell module that supports Azure multi-factor authentication (MFA) see: https://technet.microsoft.com/en-US/library/ms.exch.eac.EXORPSMFAModuleLearnMore(EXCHG.150).aspx?v=15.1.860.4&l=1&s=BPOS_S_E15_0

 
 

Tags: , , , , , , , , ,

Office 365 – Access denied when trying to connect to Exchange Online PowerShell

When trying to connect to Exchange Online PowerShell using the code below with a global administrator account, I was getting an access denied error:

$usercredential = get-credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri<span class="Apple-converted-space"> </span>https://outlook.office3
65.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
New-PSSession : [outlook.office365.com] Connecting to remote server outlook.office365.com failed with the following
error message :
[ClientAccessServer=VI1PR08CA0018,BackEndServer=,RequestId=1c6b263f-08cf-4885-937c-e9c9808ddf89,TimeStamp=1/12/2017
2:46:41 PM] Access Denied For more information, see the about_Remote_Troubleshooting Help topic.
At line:1 char:12
+ $Session = New-PSSession -ConfigurationName Microsoft.Exchange -Conne ...
+            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : OpenError: (System.Manageme....RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
gTransportException
+ FullyQualifiedErrorId : -2144108477,PSSessionOpenFailed

I was able to succesfully logon to https://login.microsoftonline.com/ so the credentials were correct.
Also I had not enabled Multi-factor authentication because I knew this could be a problem.
I tried clearing credentials / cookies in browsers and credential manager.

Since this did not solve the issue, I started to search online for answers. The first result was : https://support.microsoft.com/en-gb/kb/2905767 which suggested I entered the wrong username/password or that I didn’t have the correct permissions (organization administrator).

Both where not the case.

I then came across this post where someone apparently had to reset the password: https://answers.microsoft.com/en-us/msoffice/forum/msoffice_o365admin-mso_manage/exchange-online-remote-powershell-access-denied/b91205bf-3419-4251-badc-4181af701d4f , but this was also not the case for me.

So I tried using the new Exchange Online PowerShell module that supports Azure multi-factor authentication (MFA) and the Connect-EXOPSSession cmdlet which did seem to work.

As it turns out, I accidentally tried to logon using <domain>\username instead of using my User Principal Name (UPN). When using my UPN it worked perfectly.

As a bonus though, now I have no excuse anymore to start testing with MFA

For more information about the new Exchange Online PowerShell module that supports Azure multi-factor authentication (MFA) see: https://technet.microsoft.com/en-US/library/ms.exch.eac.EXORPSMFAModuleLearnMore(EXCHG.150).aspx?v=15.1.860.4&l=1&s=BPOS_S_E15_0

 
Leave a comment

Posted by on January 16, 2017 in Microsoft, Office, Office 365, Powershell

 

Tags: , , , , , , , ,

 
%d bloggers like this: