RSS

Tag Archives: test

#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: , , , , , , , , , , , , , , , ,

Windows 8 – Running Windows 8 from USB stick or USB hard disk using Windows To Go (WTG)

Summary of what Windows To Go (WTG) is

In short, Windows To Go is a Windows 8 Enterprise feature that allows you to boot and run Windows 8 from a USB flash drive (USB stick).

Potential reasons and scenarios for using Windows To Go

There are many potential reasons and scenarios for using Windows To Go, but here are some of them from the top of my head:

Read the rest of this entry »

 
4 Comments

Posted by on January 24, 2013 in ICT, Microsoft, Windows, Windows 2012, Windows 8

 

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

SCOM2012 – Quick test lab setup OpsMgr 2012 SP1 Beta using prepared VHD

Since I’m currently working with System Center Operations Manager 2012 (also often referred to as SCOM 2012 or OpsMgr 2012), I needed a testing environment.

Because time is precious, I want to minimize the time and effort needed to set it up. This is what I did:

  1. Install and configure Hyper-V on my Windows 8 laptop.
  2. Deploy a VM with Server 2012 Datacenter edition.
    Configure a static IP address.
    Configure AD DS / domain controller role.
    Configure DHCP server role.
    Configure DNS server role (if desirable, include DNS forwarder for internet DNS queries).
  3. Download and extract the pre-configured SCOM 2012 SP1 beta VHD (running Server 2012 Datacenter edition). System Center 2012 SP1 adds Windows Server 2012  SQL Server 2012 Support.
  4. Create a new VM for SCOM and configure it to use the downloaded pre-configured VHD.
    Configure it with sufficient virtual CPUs and memory  for the DB and the SCOM components.
  5. Start the SCOM VM, and follow the onscreen instructions. NOTE: First time configuration took about 1-2 hours, but this might be related to my hardware. Still it might be smart to plan the installation.
  6. Convert the Server 2012 Datacenter edition from ServerDatacenterEval to ServerDatacenter by running an elevated CMD as administrator and issuing the following command:
    DISM /online /Set-Edition:ServerDatacenter> ProductKey:<productkey> /AcceptEula
    Reboot.
  7. Replace the SCOM 2012 evaluation license with your own license using Operations Manager Shell:
    Set-SCOMLicense -ProductId “yourlicensekey“
    Reboot
    Check the license using the Operations Manager Shell:
    Get-SCOMManagementGroup | ft skuforlicense, version, timeofexpiration –a
  8. Shut down your SCOM 2012 VM.
  9. Shut down your AD DS / domain controller VM.
  10. Create a snapshot and/or export of both VM’s so you can more easily revert to a good starting point.

Also be sure to read Stefan Stranger’s blog post that goes into more detail with regards to the domain controller and automating the task for setting it up. It also includes screenshots to give you a better idea of the process.

PS: I love how Microsoft provides these pre-configured VHD’s. This has been great in the past, but now with client Hyper-V 3.0 in Windows 8 it saves me even more time because I don’t have to boot to Windows Server 2008 or Windows Server 2012 for Hyper-V.

 

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

 
%d bloggers like this: