Social

onsdag den 21. oktober 2015

Pinging with Powershell - Get a list of online servers

Just about every IT guy or girl on the planet has used ping countless of times. With Powershell those days are over: Meet Test-Connection. It works just as you would expect and similar to ping, the advantage being that you (by default) get an object of type "System.Management.ManagementObject#root\cimv2\Win32_PingStatus" back.

I wont go into details on how to use Test-Connection, there are plenty of ressources doing just that. But I recently discover a very cool way to use Test-Connection against a large number of servers. And as they say, A script is worth a thousand words:

function Check-Online
{
    param(
        $ComputerName
        )

    Begin
    {
        # put live servers into this list
        $Script:serverlist = @()
    }

    Process
    {
        If (Test-Connection -Count 1 -ComputerName  $_ -TimeToLive 5 -asJob | 
         Wait-Job |
         Receive-Job |
         ? { $_.StatusCode -eq 0 } )
        {
            $Script:serverlist += $_
        }
    }
    End
    {
        return $Script:serverlist
    }
}

# get computer names from hyper-v
$ComputerNames = Get-VM | Select-Object -ExpandProperty Name
# check which are online
$ComputerNames | Check-Online

Simply feed the Check-Online function a list of computernames (IPs should work just as well) and it will return a list of online servers within seconds.

I think credit for the original code goes to my Lumagate colleague Claus Nielsen who just happens to be a Powershell MVP.

Download script from Technet gallery.

fredag den 16. oktober 2015

Reattaching Drives on VMs Using Powershell (General access denied on vhdx)

I recently had some storage issues in the company lab which meant that after a lengthy CHKDSK that permission on all VHDX files was lost. The solution was luckily simple: Reattach each drive and the permission on the file was restored. Problem was then that there was more than 50 drives all in all. Solution then became, as it often is, do it with Powershell.

The script as follows (Download from Technet), formatted using https://tohtml.com/powershell/:

$States = ( [Microsoft.HyperV.PowerShell.VMState]::Off, 
            [Microsoft.HyperV.PowerShell.VMState]::OffCritical
          )

$VMs = Get-VM | ? {$_.State -in $States}
$DriveCount = ($VMs | Get-VMHardDiskDrive).Count
$Counter = 0

foreach($VM in $VMs)
{
    $Drives = $VM | Get-VMHardDiskDrive
    foreach($Drive in $Drives)
    {
        $Counter += 1
        # Some of these values (Path at least) disappear from the $Drive object when we remove it from the machine
        $ControllerNumber = $Drive.ControllerNumber
        $ControllerLocation = $Drive.ControllerLocation
        $Path = $Drive.Path
        $SupportPersistentReservations = $Drive.SupportPersistentReservations
        $ControllerType = $Drive.ControllerType

        Write-Progress  -Activity "Reattaching drives" `
                        -Status "Removing $Path from $($VM.Name)" `
                        -PercentComplete (100*$Counter/$DriveCount) 
        Remove-VMHardDiskDrive -VMHardDiskDrive $Drive
        
        if($SupportPersistentReservations)
        {
            # Shared vhdx
            Add-VMHardDiskDrive -VM $VM `
                                -ControllerNumber $ControllerNumber `
                                -ControllerLocation $ControllerLocation `
                                -Path $Path `
                                -ControllerType $ControllerType `
                                -SupportPersistentReservations
        }
        else
        {
                        Add-VMHardDiskDrive -VM $VM `
                                -ControllerNumber $ControllerNumber `
                                -ControllerLocation $ControllerLocation `
                                -Path $Path `
                                -ControllerType $ControllerType
        }
        
        Write-Progress  -Activity "Reattaching drives" `
                        -Status "Reattached $Path to $($VM.Name)" `
                        -PercentComplete (100*$Counter/$DriveCount)
    }
}

tirsdag den 6. oktober 2015

Creating Multiple Azure NICs Using Multiple Instances (ARM Template)

I have started playing around with ARM and Azure in general and wanted to get my feet wet with linked templates and multiple instances. My very gifted colleague Kristian Nese has already covered template linking just fine, but I have yet to find a simple example on how to use multiple instances (honestly, I didn't try that hard, I want to do it myself).

Anyways, I thought I would share what I have done so far. It is a good introduction to multiple instances in ARM templates, and also get to use a few of the template functions. I will not provide a full solution (strongly suggest you piece it together yourself for the learning experience), but rather snippets and explanations (to the best of my knowledge).

If you are new to ARM templates: go away. No, just kidding, but do come back when you have the basics covered. I attended this hands on lab recently and found the excercises was an excellent learning experience. The teacher suggested not to use Visual Studio as you miss out on learning some of the basics of ARM templates. Follow that advice and just use your prefered json-editor (Sublime Text 2).

We will jump right in. The following snippet creates multiple NICs in Azure based on a parameter namePrefixes which we use to prefix various resources.

"namePrefixes": {
      "type": "array",
      "defaultValue": [
        "dc",
        "sql",
        "scsm"
      ]
    }

The resource NICs are declared as follows

{
  "dependsOn": [
    "[concat('Microsoft.Resources/deployments/',  parameters('vnetName'))]",
    "[concat('Microsoft.Network/publicIPAddresses/', parameters('namePrefixes')[copyIndex()], '-', variables('pulicIPPostfix'))]"
  ],
  "name": "[concat(parameters('namePrefixes')[copyIndex()], '-', variables('nicPostfix'))]",
  "type": "Microsoft.Network/networkInterfaces",
  "location": "[resourceGroup().location]",
  "apiVersion": "2015-06-15",
  "copy": {
    "name": "nicCopy",
    "count": "[length(parameters('namePrefixes'))]"
  },
  "properties": {
    "ipConfigurations": [
      {
        "name": "[concat('ipconfig', copyIndex())]",
        "properties": {
          "privateIPAllocationMethod": "static",
          "privateIPAddress": "[concat(variables('addressPrefixSplit')[0], '.', variables('addressPrefixSplit')[1], '.', variables('addressPrefixSplit')[2], '.', add(copyIndex(), 4))]",
          "subnet": {
            "id": "[variables('subnetID')]"
          },
          "publicIPAddress": {
            "id": "[resourceId('Microsoft.Network/publicIPAddresses',concat(parameters('namePrefixes')[copyIndex()], '-', variables('pulicIPPostfix')))]"
          }
        }
      }
    ]
  }
}

First we need to indicate that the nics depend on a virtual network. Here I am using Microsoft.Resources/deployments because the virtual network has been deployed using a linked template. We also depend on some public ip addresses that we have created using multiple instances also.
The naming scheme is to iterate the namePrefixes array and postfix the variable nicPostfix (mine has the value "nic").
Now in order to provide static IP addresses we use the variable addressPrefixSplit which is defined as

"addressPrefixSplit": "[split(parameters('addressPrefix'), '.')]"


We simply split the addressPrefix parameter on '.', ex. 10.0.0.0/16 which is also used to create the virtual network. The IP address is then the first 3 octets of the addressPrefix and the 4th octet is the value of copyIndex() + 4 which would give us the addresses: 10.0.0.4, 10.0.0.5, and 10.0.0.6.

The public IP Address is a reference to the resource ID of public IP addresses created using the same approach:

{
      "apiVersion": "2015-06-15",
      "type": "Microsoft.Network/publicIPAddresses",
      "location": "[resourceGroup().location]",
      "name": "[concat(parameters('namePrefixes')[copyIndex()], '-', variables('pulicIPPostfix'))]",
      "copy": {
        "name": "pipCopy",
        "count": "[length(parameters('namePrefixes'))]"
      },
      "properties": {
        "publicIPAllocationMethod": "[variables('publicIPAllocationMethod')]"
      }
}

Now you are ready to deploy a billion trillion NICs with just a few lines of json (not counting the billion trillion lines of name prefixes :D)

onsdag den 15. juli 2015

Deploying Custom Console Tasks Using VSAE

Not too long ago I showed how to deploy your custom code to Service Manager using a script. What I later found out is that VS can do it all for you with the help of VSAE.
I will base the code off of my previous blog post, and I suggest you read it before continuing. Also get acquainted with VSAE in Authoring Type Projections in VSAE. I will not repeat some of the principles mentioned there.

First we open the solution that contains your code. Add an additional project to this solution and select the "Service Manager 2012 R2 Management Pack" template (or whatever version that suits your environment). You will need to add references to the following management packs
  • Microsoft.EnterpriseManagement.ServiceManager.UI.Console
  • ServiceManager.ConfigurationManagement.Library
  • ServiceManager.WorkItem.Library
They can be found in ex. the library folder of the Authoring Tool installation path.

We also add the dll we are coding as a reference. When added select it and click F4 (properties) and change Package To Bundle to True. This means it will be included in the .mpb file we are building.

Add a new item and select "Empty Management Pack Fragment". We will call it ConsoleTask.mpx. It doesn't matter what you call all of these mpx-files. The names will not appear anywhere in the final product.


Copy paste in the code below:

<ManagementPackFragment SchemaVersion="SM2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Categories>
    <!-- provide an ID for the category -->
    <Category ID="CB.ConsoleTask.CustomWISearch.Category" 
              Value="MESUC!Microsoft.EnterpriseManagement.ServiceManager.ManagementPack">
      <!-- this is the same name as the VS project or the management pack ID found in project properties -->
      <ManagementPackName>CB.ConsoleTask.CustomWISearch</ManagementPackName>
      <ManagementPackVersion>1.0.0.0</ManagementPackVersion>
      <!-- you can get the public key token using sn.exe, read more at http://scsmnz.net/sealing-a-management-pack-using-fastseal-exe/ -->
      <ManagementPackPublicKeyToken>098dab1c6092cc7a</ManagementPackPublicKeyToken>
    </Category>
  </Categories>
  <Presentation>
    <ConsoleTasks>
      <!-- provide an ID for the console task -->
      <!-- provide a Target for the console task -->
      <ConsoleTask ID="CustomSearchTask.ConsoleTask" 
                   Accessibility="Public" 
                   Enabled="true" 
                   Target="MESUC!Microsoft.EnterpriseManagement.ServiceManager.UI.Console.ConsoleTaskTarget" 
                   RequireOutput="false">
        <Assembly>MESUC!SdkDataAccessAssembly</Assembly>
        <Handler>Microsoft.EnterpriseManagement.UI.SdkDataAccess.ConsoleTaskHandler</Handler>
        <Parameters>
          <!--Name of the assembly file without extension -->
          <Argument Name="Assembly">CB.SCSM.CustomWISearch</Argument>
          <!-- classname of your ConsoleCommand task including namespace -->
          <Argument Name="Type">CB.SCSM.CustomWISearch.CustomSearchTask</Argument>
        </Parameters>
      </ConsoleTask>
    </ConsoleTasks>
    <FolderItems>
      <!-- Show in Work Item root -->
      <FolderItem ElementID="CustomSearchTask.ConsoleTask" 
                  ID="CustomSearchTask.ConsoleTask.FolderItem" 
                  Folder="SWL!ServiceManager.Console.WorkItem.Root"/>
      <!-- Show in CI root -->
      <FolderItem ElementID="CustomSearchTask.ConsoleTask"
                  ID="CustomSearchTask.ConsoleTask.FolderItem"
                  Folder="SCL!ServiceManager.Console.ConfigurationManagement.ConfigItem.Root"/>

      </FolderItems>
  </Presentation>

  <Resources>
    <!-- 
    ID is normally not used if ever
    Filename including extension (.dll)
    QualifiedName - include PublicKeyToken if you sign the assembly (strongly suggested)
    -->
    <Assembly ID="Assembly.ConsoleTask.CustomWISearch" 
              Accessibility="Public" 
              FileName="CB.SCSM.CustomWISearch.dll" 
              HasNullStream="false" 
              QualifiedName="CB.SCSM.CustomWISearch, Version=1.0.0.0, Culture=neutral, PublicKeyToken=098dab1c6092cc7a" />
  </Resources>
</ManagementPackFragment>

It works right off the bat with the Custom Task for Work Item Search in Service Manager as mentioned earlier. And should be fairly easy to change as needed using the provided comments.

We will also do abit of "localization". I haven't found the way to do localization in custom console tasks, so this is just one way to do it. If using my code here is a nice optimization for connecting to the management server. I haven't gotten around changing the downloadeable source yet.

//Connect to the server
Microsoft.EnterpriseManagement.UI.Core.Connection.IManagementGroupSession session = (Microsoft.EnterpriseManagement.UI.Core.Connection.IManagementGroupSession)FrameworkServices.GetService<IManagementGroupSession>();
if(session == null)
{
    //Get the server name to connect to
    String strServerName = Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\System Center\\2010\\Service Manager\\Console\\User Settings", "SDKServiceMachine", "localhost").ToString();
    // connect
    _mg = new EnterpriseManagementGroup(strServerName);
}
else
{
    _mg = session.ManagementGroup;
}

It piggybacks the existing connection in place of connecting a new one.

Create two more empty management pack fragments and paste in the code below. It doesn't matter what you call them, I would suggest something along the lines of LocalizationLANG.mpx where LANG is the language of the displaystrings contained.

<ManagementPackFragment SchemaVersion="SM2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Presentation>
    <StringResources>
      <StringResource ID="SearchDialogTitle.StringResource"/>
      <StringResource ID="SearchDialogSearchBtn.StringResource"/>
    </StringResources>
  </Presentation>
  <LanguagePacks>
    <LanguagePack ID="ENU" IsDefault="true">
      <DisplayStrings>
        <DisplayString ElementID="CB.ConsoleTask.CustomWISearch">
          <Name>CB.ConsoleTask.CustomWISearch</Name>
        </DisplayString>
        <DisplayString ElementID="CustomSearchTask.ConsoleTask">
          <Name>Search!</Name>
        </DisplayString>
        <DisplayString ElementID="SearchDialogTitle.StringResource">
          <Name>Search!</Name>
        </DisplayString>
        <DisplayString ElementID="SearchDialogSearchBtn.StringResource">
          <Name>Search!</Name>
        </DisplayString>
      </DisplayStrings>
    </LanguagePack>
  </LanguagePacks>  
</ManagementPackFragment>

The other one you can fill in your language of choice.

In the project properties  we go to build and check "Generate sealed and signed management pack" and select the .snk file to use for signing the management pack. In the Management group tab add and set the proper management group as default.


And finally in the Deployment tab you check auto-increment version, start action must be "Deploy projects to default management group only" and projects to deploy is "Deploy StartUp projects only". The startup project can be selected in solution properties.


You should be good to go. First build using ctrl+shift+b (or right click solution and select build solution). Resolve any errors. When error-free (let me know in the comments below if I missed something) we can deploy the project by hitting F5.

mandag den 29. juni 2015

Authoring Type Projections in VSAE

It seems that VSAE is not that widely used. That may be a lack of information on the hows and the whats of things. So today I will just show something basic; authoring a type projection.

The VSAE can be downloaded from here. You will need Visual Studio 2012/13 Ultimate or Professional.

Now in Visual Studio create a new project. Select Templates->Management Pack->Service Manager and select the template that matches your environment version. I will pick the R2 one. I will name mine Codebeaver.IR.TypeProjection.Tutorial.

I will do a type projection on the incident class, hence we will need to reference the MP where this is defined. If you don't know this there are a few ways to find out. I prefer using powershell. Open a Service Manager Shell which will load the native service manager powershell module. Enter

Get-SCSMClass -Name "*incident" -ComputerName SM01

This tells it to look for classes that matches *incident (anything followed by incident) and the computername is the name of your management server.
This will give you three results. We are looking for the System.WorkItem.Incident class. Repeat the command with this more specific name. To get the management pack we can write

(Get-SCSMClass -Name "System.WorkItem.Incident" -ComputerName SM01).getmanagementpack()

Which tells us that the incident class is found in the System.WorkItem.Incident.Library management pack.
I find the easiest approach is to go to C:\Program Files (x86)\Microsoft System Center 2012\Service Manager Authoring (or where ever you have installed the authoring console), and then simply search for the managegement pack. When found right click and "open file location". Copy the path.

Now back in visual studio right click the references and "Add reference..."


Click the browse tab and paste in the path. Scroll and look for the System.WorkItem.Incident.Library.mp file. Now that it is added we can reference it using an auto genereated alias.

Now right click the project and select Add-> New item... Pick the "empty management pack fragment". I will name mine IncidentTypeProjection.mpx.
You will be presented with some xml. Type < and a number of possible XML-tags are suggested.

It will narrow down the list as you type. We want TypeDefinitions, and inside that EntityTypes, and finally inside that TypeProjections. VSAE will mostly present you with valid XML. 


Inside the TypeProjections tag we enter TypeProjection and then a space and you get to pick amongst a number of possible attributes for that tag. Sometimes it can even autocomplete the value for a given attribute, ex. Accesibility (as there are only two possible values). You can put anything into the ID. For type we want something that looks like: Alias!Class. To get the alias select the management pack reference that we just added and hit F4. Here you can see that the alias is SWIL. Hence we enter SWIL!System.WorkItem.Incident, and we can finish the type projection with a >. This is a good time to build (ctrl+shift+b). Resolve any errors (there should be none).



Now we must add a component to the type projection. You must provide the component with an alias (type in anything). the Path is a bit more tricky, and VSAE will not help you one bit. I suggest you read my bit on type projections before you continue reading. To get the relationship part we use the same trick as we did for getting the class. Here is a little help:

Get-SCSMRelationshipClass -Name "created" -ComputerName SM01 | fl Name

My component ends up looking

<Component Alias="IsCreatedBy" Path="$Target/Path[Relationship='SWL!System.WorkItemCreatedByUser']$"/>

And no errors when building! Be really carefull with $ and ' and [] at the right places.

We also need to add a section of display strings to finish up. After the closing TypeDefinitions add a LanguagePacks tag. It will end up looking like this

  <LanguagePacks>
    <LanguagePack ID="ENU" IsDefault="true">
      <DisplayStrings>
        <DisplayString ElementID="ThisCanBeAnythingAsLongAsItIsUnique.TypeProjection">
          <Name>
            Incident (Is created by)
          </Name>
        </DisplayString>
      </DisplayStrings>
    </LanguagePack>
  </LanguagePacks>

Note that the LanguagePack ID must a valid ID. I haven't found a table with all possible IDs (post in the comments below if you do).

We are almost there. Go to properties of the project and in the build tab check "generate sealed and signed management pack". Browse for a key file and select your snk-file (if you don't know this part read this to get up to speed - you just need to read the part on Create your SNK).
In the Management group tab click Add and enter your Service Manager Management Server (you only need add it once), if already added select it and click "Set as Default". Finally in the Deployment tab under "start action" select "Deploy projects to default management group only".

Build again and make sure there are not errors. Now we can deploy the type projection directly to the server by hitting F5. That is pretty sweet and can really speed up your development even for basic stuff such as type projections.

The entirety of the fragment looks as below

<ManagementPackFragment SchemaVersion="SM2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <TypeDefinitions>
    <EntityTypes>
      <TypeProjections>
        <TypeProjection ID="ThisCanBeAnythingAsLongAsItIsUnique.TypeProjection" 
                        Accessibility="Public" 
                        Type="SWIL!System.WorkItem.Incident">
          <Component Alias="IsCreatedBy" 
                     Path="$Target/Path[Relationship='SWL!System.WorkItemCreatedByUser']$"
                     />
        </TypeProjection>
      </TypeProjections>
    </EntityTypes>
  </TypeDefinitions>
  <LanguagePacks>
    <LanguagePack ID="ENU" IsDefault="true">
      <DisplayStrings>
        <DisplayString ElementID="ThisCanBeAnythingAsLongAsItIsUnique.TypeProjection">
          <Name>
            Incident (Is created by)
          </Name>
        </DisplayString>
      </DisplayStrings>
    </LanguagePack>
  </LanguagePacks>
</ManagementPackFragment>

onsdag den 24. juni 2015

Custom Task for Work Item Search in Service Manager

You can often find what you are looking for using the advanced search feature in Service Manager, but the process is tedious as it only remembers the class you last searched for. If you often use the exact same search queries then the great Anton Gritsenko have a solution with the option of saving your queries. Unfortunately this solution cannot change the query once saved.
A middle ground would be to have a custom task that takes one or more inputs and queries a specific class based on that input.

Beaver Dams Inc. sometimes include invoice numbers in the description field of incidents and service request. This does not happen often enough to justify extending both classes with a specific field for the invoice number. Also both types of work items are not annotated in any way in order to identify which incidents and service requests contains the invoice numbers.

The solution is a query on the description property of the work item class. The description must contain a variable string (the invoice number). This is fairly easy to do using advanced search, but does require you to add the desription property in each search (it remembers the class picked in each session). As I will show it is also not that difficult (once you know how) to create a custom console task that executes the query with a variable property value.

Disclaimer: A lot of this code is heavily inspired by the aforementioned solution by Anton. 

First order of business is actually installing the "Advanced Search with Saver". It will cleverly hook into the default advanced search and ask if you wish to save the query. Search for objects of the class Work Item and add the property Description. Doesn't matter what you input, this will be the variable component of the query in the console task.


Likely nothing will turn up. After closing the window you will be prompted to save the query. Click "yes" and enter a query name. Name it something that you can easily find in the registry later on, ex. iwetmybed. Start the registry editor and search for the query name in HKEY_USERS. I will not show it here as it is quite a lot of text. The part we want to concern ourselves with looks somewhat like this:

<Expression>
  <SimpleExpression>
    <ValueExpressionLeft>
      <Property>$Context/Property[Type='f59821e2-0364-ed2c-19e3-752efbb1ece9']/e5162c95-9469-924c-2298-9e351e0dc383$</Property>
    </ValueExpressionLeft>
    <Operator>Like</Operator>
    <ValueExpressionRight>
      <Value>%whatever%</Value>
    </ValueExpressionRight>
  </SimpleExpression>
</Expression>

That looks familiar! The entire query will be inserted as a giant block of text into our code (remeber to escape the double quotes) where whatever will be replaced by a string variable. This variable could come from anywhere; I have a window popup and ask for the invoice number.

The core of the code then looks like (omitting some of the query for brevity)

SearchNodeProvider searchNodeProvider = (SearchNodeProvider)FrameworkServices.GetService(typeof(SearchNodeProvider));

String sDescriptionContains = (String)inputWindow.InputText.Text;

String advancedSearchConfiguration = "<Data>..." +
  "<Expression>" +
    "<SimpleExpression>" +
      "<ValueExpressionLeft>" +
        "<Property>$Context/Property[Type='f59821e2-0364-ed2c-19e3-752efbb1ece9']/e5162c95-9469-924c-2298-9e351e0dc383$</Property>" +
      "</ValueExpressionLeft>" +
      "<Operator>Like</Operator>" +
      "<ValueExpressionRight>" +
        "<Value>%" + sDescriptionContains + "%</Value>" +
      "</ValueExpressionRight>" +
    "</SimpleExpression>" +
  "</Expression>" +
"</Criteria> ...";

searchNodeProvider.AdvancedSearchConfig = advancedSearchConfiguration;

Uri uri = new Uri(((object)NavigationModel.NavigationRoot).ToString() + 
                    "Windows/Search/ConsoleDisplay/Advanced");
NavigationModel.BeginOpenLink(
    NavigationModel.FindView((object)null, 
    (Uri)Microsoft.EnterpriseManagement.UI.Core.Shared.NavigationConstants.ConsoleWindowUri, 
    (FindViewCriteria)1), 
    uri, 
    null, 
    (object)null);


I will not pretend to know exactly what is going on. I was "inspired" by a decompiled version of the Advanced Search with Saver. But as you can see the SearchNodeProvider is fed a search configuration (the one we got from registry). The NavigationalModel is then told to open the specified URI, and voila the search results window is opened with the found objects that matches the search configuration we fed the SearchNodeProvider.

You can download the entire source here. Happy coding.

Edit: It is needed to change the thisMPID variable in LocalizationHelper.cs to the guid of the management pack that contains the stringresources used. In the example code, this is also where the console task is defined, hence you will have to import the bundle, get the MP id, change the code, compile, bundle, and import again. I suggest looking at my previous blogpost to automate this process.


tirsdag den 23. juni 2015

Automating Custom Code Deployment in Service Manager

Sounds too good to be true? Well it is. Almost. The script I will be sharing today will get you a long way though. A high level rundown of what it will do

  •  stop console
  •  seal MP
  •  bundle MP
  •  remove old MP
  •  import new MP bundle
  •  start console
That almost gets you all the way. You will have to do the clicking yourself. You can then debug the hell out of your code following this.

Script as follows, and download it here.


# Authored by Anders Spælling, spaelling@gmail.com

# this script automates some of the tasks needed to test custom code in the Service Manager console
# a high level rundown:
# stop console
# seal MP
# bundle MP
# remove old MP
# import new MP bundle
# start console

# This script requires 
# module ScsmPx installed. Just run this if you haven't got it already:  & ([scriptblock]::Create((iwr -uri http://tinyurl.com/Install-GitHubHostedModule).Content)) -ModuleName ScsmPx,SnippetPx
# need to have fastseal.exe in working dir, download from here: http://blogs.technet.com/cfs-file.ashx/__key/communityserver-components-postattachments/00-03-30-25-60/FastSeal.zip
# guide on fastseal.exe http://scsmnz.net/sealing-a-management-pack-using-fastseal-exe/

$DebugPreference = "Continue"
# this needs to point at where the compiled dll will end up        
$FullPathDLL = "C:\Users\Administrator\Documents\Visual Studio 2013\Projects\CB.SCSM.CustomWISearch\CB.SCSM.CustomWISearch\bin\Debug\CB.SCSM.CustomWISearch.dll"

# you will need an snk file. create your own. Follow this guide if you don't know how: http://scsmnz.net/sealing-a-management-pack-using-fastseal-exe/

# files distributed with this script also needs to go to working dir

# this is the working dir
cd c:\temp

# point at your SCSM management server
$ManagementServer = "SM01"
# change to match your own snk file
$KeyFile = "CB.snk"
# MP name without extension
$MPName = "CB.ConsoleTask.CustomWISearch"
$Company = "Codebeaver"

# not much need to edit below

Import-Module ScsmPx -ErrorAction Stop

# stop console
Write-Debug "Stopping console..."
Stop-Process -Name Microsoft.EnterpriseManagement.ServiceManager.UI.Console -ErrorAction SilentlyContinue

# seal MP
Write-Debug "Sealing MP..."
$ArgumentList = ".\$MPNameh.xml /keyfile $KeyFile /company $Company"; #Write-Host "fastseal.exe $ArgumentList"
Start-Process -FilePath ".\fastseal.exe" -ArgumentList $ArgumentList -Wait -NoNewWindow

# copy dll to c:\temp
Copy-Item $FullPathDLL -Destination c:\temp

# bundle MP and dll
Write-Debug "Bundling MP..."
$DLL = $FullPathDLL.Split("\")[-1]
New-SCSMManagementPackBundle -Name "$($MPName).mpb" -ManagementPack ".\$($MPName).mp" -Resource ".\$($DLL)" -ComputerName $ManagementServer -Force -ErrorAction Stop

# remove previous MP (cannot overwrite sealed MP with same  MP version)
# If you delete a sealed MP, all of the data that it defined such as new classes (and all instances of these classes) or class extensions (and all extension data) will be lost.
$MP = Get-SCSMManagementPack -Name CB.ConsoleTask.CustomWISearch # name is the ID of the MP (as defined in xml)
if($MP)
{
    Write-Debug "Removing old MP..."
    Remove-SCSMManagementPack -ManagementPack $MP
}

# import MP bundle in new process - current process will block the file otherwise
Write-Debug "Importing new MP bundle..."
$MPName | powershell.exe {
    Import-SCSMManagementPack ".\$($input[0]).mpb"
    }

# start console
Write-Debug "Starting console..."
& "C:\Program Files\Microsoft System Center 2012 R2\Service Manager\Microsoft.EnterpriseManagement.ServiceManager.UI.Console.exe"

tirsdag den 3. marts 2015

Nordic Cloud Management Summit 2015

Shamelessly exploiting my own personal blog to promote an event at work. Want to know more about Azure? This is the event for you. Sign up before your neighbor. There will be free snacks!

Leave a comment below if you do. That will get my boss off my back ;)

For ease of reference:
  • Sweden/Stockholm: March 24th
    • Microsoft, Finlandsgatan 30, 164 74 Kista
  • Finland/Helsinki: March 25th
    • Microsoft, Keilaranta 7, 02150 Espoo
  • Denmark/Copenhagen: March 26th
    • Microsoft, Tuborg Boulevard 12, 2900 Hellerup
  • Norway/Oslo: April 9th
    • Microsoft, Lysaker Torg 45, 1366 Lysaker

onsdag den 28. januar 2015

Personal Update

This blog just broke the 10.000 pageviews, I just broke 1.000 points on Technet, and I am starting a new job at Lumagate as Senior System Center Consultant!

So thing are going pretty well in my professional career. I will be taking a bunch of certifications over the next few months (being autodidact doesn't count for much in this regard), and therefore I doubt there will be much time for blogging and helping the poor unfortunate souls on Technet.

Also summer is coming.... And I am quite the garden enthusiast, and looking forward to spending lots of time out in the sun, tending my precious plants :D Yes, I have way too many hobbies, and just can't find time for it all.

Anyways, thanks for reading.

onsdag den 21. januar 2015

Submitting a SCSM Request Offering Programmatically

As part of the employee onboarding process there is a request offering on the portal that HR fills out and submits. But recent automation has made a file available with the same information as was entered into the portal. Next step is reading the file and submitting the same request offering programatically (and 100% autonomous).

Using the script I just wrote one can do:

Import-Csv -Path \newemployees.txt -Delimiter ';' | 
    % {\Submit-SCSMRequestOffering.ps1  -RequestOffering $RequestOffering `
                                        -MyInput @($_.firstname, $_.surname, $_.salary)}

Imports a csv-file and submits as many request offerings as there are lines (excluding headers) in the file. One could also just submit a single request offering:


\Submit-SCSMRequestOffering.ps1  -RequestOffering $RequestOffering -MyInput @(1,2,3)

The input corresponds to the questions given in the request offering, and the answer mapping is retained. This is especially useful in flows with many activities where the input to the request offering must be available. Coupled with another script i wrote (Including extension properties in the description field) the activities become a breeze to complete, everything you need right there in the description in a nice prose.

Download the script from Technet Gallery. Remember to rate!

tirsdag den 20. januar 2015

Service Manager Management Pack Backup Script

Are you doing Management Pack backups? Well, you should. Schedule the script to run daily (during off-hours).

I have had this lying around for a while now, and thought I would share. Script as follows (Download here - please rate):


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
###############################################################
# Backup service manager management packs
#
# Authored by:
# Anders Spælling, spaelling@gmail.com
#
###############################################################

# EVENT IDs
# 800 - Warning     - Backup completed with errors
# 700 - Error       - Unable to import module
# 701 - Error       - Failed to backup unsealed MPs
# 702 - Error       - Failed to backup sealed MPs
# 703 - Error       - Failed to create new backup folder
# 600 - Information - Succesfully backed up unsealed MPs
# 601 - Information - Succesfully backed up sealed MPs
# 602 - Information - Removed old backup (unsealed MPs)
# 603 - Information - Removed old backup (sealed MPs)
# 604 - Information - Backup done
# 605 - Information - Starting MP backup job

# CONSTANTS
# Write to event log on this computer
$EventLogComputerName = ''
$EventLogName = "SCSM backup task"
$Date = Get-Date 

# user defined #
# definde rootpath to save managemen packs. Should be UNC format
$RootPath = '\SCSM_MP\'
# Define service manager management server
$SMMS = $EventLogComputerName

# keep MP backups for this many days
$BACKUP_RETAIN_IN_DAYS = 28

# increase if failed to backup MPs
$ErrorCount = 0

# used to write to event log
# Example use, create event with event ID 702 and type Error: CreateEventLog "Error description" 702 "Error"
Function CreateEventLog
{
    Param($EventDescription,$EventID,$Type)
    $EventlogExists = Get-EventLog -ComputerName $EventLogComputerName -List | Where-Object {$_.LogDisplayName -eq $EventLogName}
    If(-not $EventlogExists) 
    {
        New-EventLog -LogName $EventLogName -Source AlertUpdate -ComputerName $EventLogComputerName 
    }
    Write-EventLog -ComputerName $EventLogComputerName -LogName $EventLogName -Source AlertUpdate -Message "$EventDescription" -EventId $EventID -EntryType $Type
}

# remove SMLets module (from session) if loaded  - needed to load the System.Center.Service.Manager module as command names overlap
if(Get-Module -Name SMLets)
{
    Remove-Module 'SMLets' 
}

# find module in '%SMinstalldir%\Powershell\System.Center.Service.Manager.psd1'
$SMInstallDir = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\System Center\2010\Service Manager\Setup').InstallDirectory
$ModuleDir = $SMInstallDir + 'Powershell\System.Center.Service.Manager.psd1'

Import-Module $ModuleDir

if(-not (Get-Module -Name System.Center.Service.Manager))
{
    CreateEventLog "Unable to import module System.Center.Service.Manager`nException message: $($_.Exception.Message)" 700 "Error"
    Exit
}

#######################
# BACKUP UNSEALED MPS #
#######################

CreateEventLog "Starting MP backup job" 605 "Information"

# Define path to save todays backup to
$Path = $RootPath + $Date.ToString('yyyy-MM-dd')

# create path if it does not exists
If (-not (Test-Path $Path -ErrorAction Stop)) 
{
    try
    {
        $CreateOutput = New-Item -ItemType Directory $Path
    }
    catch [System.Exception]
    {
        CreateEventLog "Unable to create new backup folder $Path`nException message: $($_.Exception.Message)" 703 "Error"
        $ErrorCount++
    }
}

try
{
    # get unsealed MPs and export to disk
    Get-SCManagementPack -ComputerName $SMMS | where{$_.sealed -eq $False} | Export-SCManagementPack -Path $Path
    CreateEventLog "Succesfully backed up unsealed MPs" 600 "Information"
}
catch [System.Exception]
{
    CreateEventLog "Failed to backup unsealed MPs`nException message: $($_.Exception.Message)" 701 "Error"
    $ErrorCount++
}
finally
{
    #
}

#####################
# BACKUP SEALED MPS #
#####################

# Define path to save todays backup to
$Path = $RootPath  +  $Date.ToString('yyyy-MM-dd') + "\sealed"

# create path if it does not exists
If (-not (Test-Path $Path)) 
{
    $CreateOutput = New-Item -ItemType Directory $Path
}
try
{
    # get sealed MPs and export to disk
    Get-SCManagementPack -ComputerName $SMMS | where{$_.sealed -eq $True -and $_.Name -like "XX*"} | Export-SCManagementPack -Path $Path
    CreateEventLog "Succesfully backed up sealed MPs" 601 "Information"
}
catch [System.Exception]
{
    CreateEventLog "Failed to backup sealed MPs`nException message: $($_.Exception.Message)" 702 "Error"   
    $ErrorCount++
}
finally
{
   #
}

###########
# CLEANUP #
###########

# remove backup folder from $BACKUP_RETAIN_IN_DAYS days ago
$DeleteFolder = $Date.AddDays(-$BACKUP_RETAIN_IN_DAYS)
$DeletePath = $RootPath + $DeleteFolder.ToString('yyyy-MM-dd')

# remove folder if it exists
If (Test-Path $DeletePath)
{
    $RemoveOutput = Remove-Item $DeletePath -Recurse
    CreateEventLog "Removed old backup of MPs in `"$DeletePath`"" 602 "Information"
}

# remove module (from session)
if(Get-Module -Name System.Center.Service.Manager)
{
    Remove-Module 'System.Center.Service.Manager' 
}

if($ErrorCount -gt 0)
{
    CreateEventLog "Backup completed with $ErrorCount errors" 800 "Warning"     
}
else
{
    CreateEventLog "Backup done" 604 "Information"
}

tirsdag den 13. januar 2015

Scripting Service Manager Documentation - Views and Request Offerings

No one likes writing documentation. Even less so updating existing documentation. But it is nice to have when you need it. Also don't just write documentation for the sake of documentation. Write it down if you need it later and is not inherently obvious from the code (or whatever). But good documentation practice is a can of worms I am not going to open here.

In the following I will present a script that helps with something as tedious as documenting views in Service Manager. Are you going to do that manually?


Also views change, and you would then have to update your documentation. You really need this script!


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# Author: Anders Spælling, spaelling@gmail.com
#
# This script can assist in documenting Service Manager views. Extracts SCSM view information to csv and html
# View description should include what the view is supposed to show (that is not obvious from the view title)
#
# Requirements: SMLets installed

# enter your service manager management server below
$SCSM_MANAGEMENT_SERVER =  "SM1"

# start of script

# remove this module if loaded
$SMModule = Get-Module -Name System.Center.Service.Manager
if($SMModule)
{
    Remove-Module $SMModule
}

Import-Module 'SMLets'
if(-not (Get-Module -Name SMLets))
{
    Write-Host "Unable to import module SMLets"
    Exit
}

# try and get it from global variable - SMLets will look for this if ComputerName is not supplied
$ComputerName = $Global:smdefaultcomputer
if(-not $ComputerName)
{
    $ComputerName = $SCSM_MANAGEMENT_SERVER
}

#get views and folders
$Views = Get-SCSMView -ComputerName $ComputerName
$Folders = Get-SCSMFolder -ComputerName $ComputerName

# target only subclasses of this class
$BaseClass = Get-SCSMClass -ComputerName $ComputerName -Name System.WorkItem$

# store output in list
$Out = @()
# progress counter
$i = 1;
# iterate over all views
foreach($View in $Views)
{

    Write-Progress -Activity "Processing views..." -Status "Processing ($i of $($Views.Count)): `"$($View.DisplayName)`"" -PercentComplete (100*$i/($Views.Count))

    if($View.DisplayName -eq $null -or $View.DisplayName.Length -eq 0)
    {
        # not sure what these are...
        # Write-Host $View.Name, has no displayname
        continue;
    }

    $ManagementPack = $View.GetManagementPack()
    $TargetClass = Get-SCSMClass -ComputerName $ComputerName -Id ($View.Target.Id)
    
    # we only want targetclasses that inherits specific baseclass
    if($TargetClass.IsSubClassOf($BaseClass))
    {
        $ParentFolders = $Folders | ? {$_.Id -in ($View.ParentFolderIds| select -ExpandProperty Guid)} | select -ExpandProperty DisplayName
        # if based on a combination class we want to know which
        # first convert to xml so that we can easily traverse the xml-string
        [xml]$Configuration = [xml]("<xmlroot>$($View.Configuration)</xmlroot>")
        # traverse...
        $Value = $Configuration.xmlroot.Data.ItemsSource.AdvancedListSupportClass.'AdvancedListSupportClass.Parameters'.QueryParameter.Value
        $TypeProjectionName = $Value.Replace('$MPElement[Name=','').Replace(']$','').Replace("'","")
        # if defined in another MP we must remove the alias
        if($TypeProjectionName.Contains('!'))
        {
            $TypeProjectionName = $TypeProjectionName.Split('!')[1]
        }

        # we now have the Id of the type projection
        $TypeProjection = Get-SCSMTypeProjection -ComputerName $ComputerName -Name $TypeProjectionName

        # if based on a basic class
        $TypeProjectionDisplayName = 'N/A'

        if($TypeProjection)
        {

            # typeprojections can share the same name. If more than one is found we use the one with a displayname
            if(([array]$TypeProjection | ? {$_.DisplayName}).Count -gt 0)
            { # select first one with a displayname
                $TypeProjection = [array]$TypeProjection | ? {$_.DisplayName} | select -First 1
            }
            else 
            { # no displaynames, select first 1
                $TypeProjection = [array]$TypeProjection | select -First 1
            }

            $TypeProjectionDisplayName = $TypeProjection.DisplayName

            # if there was no displayname we use the name
            if(-not $TypeProjectionDisplayName)
            {
                $TypeProjectionDisplayName = $TypeProjection.Name
            }
        }

        $Out += New-Object PSObject -Property @{
            Title = $View.DisplayName;
            Description = $View.Description;
            TargetClass = $TargetClass.DisplayName;
            TypeProjection = $TypeProjectionDisplayName;
            ManagementPack = $ManagementPack.DisplayName;
            ParentFolder = $ParentFolders;
            VisibleInUI = $View.Visible;
        };
    }
    
    # update progress counter - used in Write-Progress
    $i++
}

# adding some style to the table
$head = @'
<style>
TABLE{border-width: 1px; border-style: solid; border-color: black;}
TH{border-width: 1px; border-style: solid; border-color: black; padding: 1px;}
TD{border-width: 1px; border-style: solid; border-color: black; padding: 1px;}
</style>
'@
# adding a title
$body = '<H2>Request Offerings</H2>'

# sort using this order
$SortOrder = 'ParentFolder', 'Title'
# select the order of properties (to output)
$Properties = 'Title', 'Description', 'TargetClass', 'TypeProjection', 'ManagementPack', 'ParentFolder', 'VisibleInUI'
# convert to html and csv
$Out | Sort-Object $SortOrder | Select-Object -Property $Properties | ConvertTo-Html -Head $head -Body $body > C:\temp\viewdoc.html
$Out | Sort-Object $SortOrder | Select-Object -Property $Properties | Export-Csv -Path C:\temp\viewdoc.csv -NoTypeInformation

You can download the code from Technet gallery. It also includes code on how to do the same with Request Offerings.

I also found this: http://www.buchatech.com/2015/03/service-manager-discovery-report/ - check it out.

Søg i denne blog