Get-World | ConvertTo-PowerShell
Archive for January, 2011
Find path to specific node in SMS console
Jan 12th
Have you ever heard this: “Hey, someone is on holiday so please take care of his work. I need to find info about specific (collection|package|advertisement). Can you please send it to me?” Sometimes it could be a nightmare. Especially if you are looking for collection named This test is what I am looking for and the structure looks like:
Not so funny. Just for this case I created small tool.
Note: Actually I have two separate tools which I want to merge to one. First is written in C# and the screenshot is taken from this one.
It’s now working only for collections and you can filter out collections live – as you are writing requested name to filter field. Second are just functions in PowerShell and I use them for queries against IDs of packages and advertisements. I will talk only about scripts in this article.
WMI behind the code
For work with hierarchy in SMS admin console there are two useful classes: SMS_ObjectContainerItem and SMS_ObjectContainerNode (based on SDK it looks a bit different for SMS2003 and SCCM2007 – of course as it uses different MMC version – so I pointed to newer version – even the script below works with SMS). This code can’t be used for collections as they used different method for storing it’s hierarchy (which is clear if you will think how it works). More info related to collections can be found in SMS_Collection and SMS_CollectToSubCollect MSDN articles.
Get-SMSConsolePath
This is the entry point to functionality. It accepts ID of package or advertisement to look for.
function Get-SMSConsolePath <# .SYNOPSIS Displays path in SMS Console. .DESCRIPTION For given SMS object (package or advertisement) shows full path to this object as it's in SMS Console. .PARAMETER id Id of the object to check .EXAMPLE Get-SMSConsolePath tst00123 Packages/Test/Testing package #> { param ( [Parameter( Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )] [ValidatePattern("tst[02]0[a-f0-9]{3}")] [string] $id ) BEGIN { $Script:ocn = Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_ObjectContainerNode -Filter "ObjectType IN (2,3)" | Select ContainerNodeID, Name, ObjectType, ParentContainerNodeID } PROCESS { $Script:SMSConsolePath = "" $Script:SMSConsoleItem = "" $container = Get-SMSConsoleContainer $id Get-SMSConsoleParent -currNode $container Write-Host "$Script:SMSConsolePath$Script:SMSConsoleItem" } END { Remove-Variable ocn -Scope Script Remove-Variable SMSConsolePath -Scope Script Remove-Variable SMSConsoleItem -Scope Script } }
You see that in begin section I load data from SMS_ObjectContainerNode and store it for future use. I am filtering the results to have just data for packages and advertisements (ObjectType=2,3). Then call two other support functions in process part and will remove some variables at the end.
Get-SMSConsoleContainer
This is the part where I’ll check actual name of the package/advertisement. I will receive it by linking result from SMS_ObjectContainerItem class and respective class of SMS_Package or SMS_Advertisement.
function Get-SMSConsoleContainer { <# .SYNOPSIS Returns containerID for given object. .DESCRIPTION For given object (based on ID) return its containerID. #> param ( $id ) $filter = "InstanceKey = '" + $id + "' AND ObjectType IN (2,3)" $tmp = Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_ObjectContainerItem -Filter $filter | Select ContainerNodeID, InstanceKey, ObjectType if ($($tmp.ObjectType) -eq '2') { $Script:SMSConsolePath += "Packages" $filter = "PackageID = '" + $tmp.InstanceKey + "'" $Script:SMSConsoleItem = (Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_Package -Filter $filter).Name return $($tmp.ContainerNodeID) } else { $Script:SMSConsolePath += "Advertisements" $filter = "AdvertisementID = '" + $tmp.InstanceKey + "'" $Script:SMSConsoleItem = (Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_Advertisement -Filter $filter).AdvertisementName return $($tmp.ContainerNodeID) } }
Get-SMSConsoleParent
And this is the function which actually resolves parent names. It works recursively from bottom to root folder in console. It uses data stored in $ocn variable and creates final string.
function Get-SMSConsoleParent { <# .SYNOPSIS Returns parent of given object. .DESCRIPTION For current node return it's parent. Used for recursion. #> param ( $currNode ) $parNode = ($Script:ocn |? {$_.ContainerNodeID -eq $currNode}).parentContainerNodeID if ($parNode -ge 0) { Get-SMSConsoleParent $parNode } $Script:SMSConsolePath += "$(($Script:ocn |? {$_.ContainerNodeID -eq $currNode}).Name)/" }
Put it together
As we have everything now (I have all three functions in one module) we can try to do some test.
PS C:\> Get-SmsConsolePath tst00035
Packages/_Global/Windows 7/Adobe/Adobe Reader X – for test
I use this code mainly for documentation purposes. Every time I am reporting to our change request system I just run another function – which uses those mentioned – and it will generate nice table for me. It’s “similar” to this:
PS C:\> remedyTable ‘Adobe Reader X’
Package:
<package info> #here is the path in console also
Collection:
<here is collection info>
Advertisement:
<same as for package>
…
So I am not spending whole day in console looking for settings are other annoying things.
Check refresh time for collections
Jan 7th
It happened to me that during the day I found one of our SMS servers a bit unresponsive. After short investigation I found out that a lot of collections is refreshing at the same time and therefore my refresh was in the queue. As the refresh is by default set for everyday, to the time when the collection was created, it can happen that there are some peaks when most of our collections are refreshing. So I decided to check this.
Looking into SMS SDK I found necessary information
and was able to check RefreshSchedule with Get-WmiObject cmdlet.
__GENUS : 2
__CLASS : SMS_ST_RecurInterval
__SUPERCLASS : SMS_ScheduleToken
__DYNASTY : SMS_ScheduleToken
__RELPATH :
__PROPERTY_COUNT : 8
__DERIVATION : {SMS_ScheduleToken}
__SERVER :
__NAMESPACE :
__PATH :
DayDuration : 1
DaySpan : 1
HourDuration : 0
HourSpan : 0
IsGMT : False
MinuteDuration : 0
MinuteSpan : 0
StartTime : 20040614175400.000000+***
As you can see, StartTime is in standard obscure WMI format and we are able to convert it to standard DateTime format easily (if you want to see more, you can check nice post from Shay Levy).
We have all info so can build short function:
function Get-SMSCollectionRefreshTime { $colls = Get-WmiObject -ComputerName vmsms01 -Namespace root\sms\site_xxx -Class SMS_Collection foreach ($coll in $colls) { $([wmi]"$($coll.__PATH)").RefreshSchedule } }
It will produce long list of refresh schedules for all our collections.
Note: You can see that in the list are some other properties and if you are interested, you can find more at SMS_ST_RecurInterval MSDN page. Most of our collections are set to refresh daily (in case of direct membership we are not refreshing at all) but we have few (three) to refresh more often. In the following code I will count all collections as if they are refreshed daily. If you have more frequently updated collections, you can think about modifying the code.
After some experimenting I came with following code:
Get-SMSCollectionRefreshTime | ` Select @{l="Hour";e={"{0:d2}" -f $([System.Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime)).Hour}} | ` Group Hour | Select Name, Count, @{l="Graph";e={"*"*$($_.Count/2)}} | Sort Name | ft -AutoSize -Wrap
which produced following result:
Name Count Graph
—- —– —–
00 16 ********
02 2 *
03 2 *
04 6 ***
05 10 *****
06 66 *********************************
07 22 ***********
08 94 ***********************************************
09 125 **************************************************************
10 163 **********************************************************************************
11 141 **********************************************************************
12 79 ****************************************
13 86 *******************************************
14 118 ***********************************************************
15 166 ***********************************************************************************
16 101 **************************************************
17 99 **************************************************
18 61 ******************************
19 33 ****************
20 12 ******
21 2 *
22 1
23 12 ******
So based on this result, most of our collections are refreshed during 10AM-11AM and 3PM-4PM intervals. Let me describe the code used.
- First Select is pretty big construction. I used computed value where the conversion from WMI time is running ([System.Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime)), from the result I then used just the hour and format it using –f operator (“{0:d2}” -f $(…)).
- Then Group it based on computed hour so can see frequency during the day. At the first time I stopped here as I already saw requested data (number of collection refreshed at specific time). But to have more fun I continued…
- Select hour, number of collections and show “graph” represented by stars. I found out that because of big peaks it’s better to multiply result by two to have all stars at one screen (“*”*$($_.Count/2)).
- Then I Sort results by hour,
- and Format result as table.
Whole work didn’t take longer than five (OK – maybe 10) minutes and I was able to confirm my first thoughts. Next task is to check collections refreshed in peaks and decide what can be changed to different time (preferably overnight). But – it will be (maybe) another post.
Note: I have small piece of paper hung at my screen in the office. It contains this citation:
I am looking to it during the day and today I confirmed it again.
How to work with KindleGen
Jan 7th
In previous post I showed you the result of my experiment with KindleGen. In this post I’ll describe the workflow I used to generate the file.
KindleGen utility
First of all you need to download KindleGen utility and unpack it to whatever folder you want. I put it to my Dropbox folder to be able to run it on all my computers and then I set an alias for kindlegen.exe file.
PS C:\> Set-Alias -Name kg -Value Dropbox:\Utilities\KindleGen\kindlegen.exe
Amazon provides two samples so can check folders Sample and MultimediaSample for general info.
Starting point – CHM file
As a source I used PowerShell help file located in C:\WINDOWS\help\WindowsPowerShellHelp.chm. CHM is “just” a bunch of HTML pages compiled together. You can see it’s structure for example via 7-Zip.
For us are important those files/folders:
- html folder – contains all topics from CHM file – one HTML page per topic.
- local folder – contains “support” files – pictures used in help, CSS file and JS file.
- PowerShell_GPSplusC.hhc – table of contents for CHM file.
You can grab all of those to your drive by using hh.exe utility.
PS C:\> Get-Command hh.exe | fl
Name : hh.exe
CommandType : Application
Definition : C:\WINDOWS\hh.exe
Extension : .exe
Path : C:\WINDOWS\hh.exe
FileVersionInfo : File: C:\WINDOWS\hh.exe
InternalName: HH 1.41
OriginalFilename: HH.exe
FileVersion: 5.2.3790.2453 (srv03_sp1_qfe.050525-1536)
FileDescription: MicrosoftR HTML Help Executable
Product: HTML Help
ProductVersion: 5.2.3790.2453
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language: English (United States)
Simply run this command:
PS C:\> hh.exe –decompile hlp c:\windows\help\WindowsPowerShellHelp.chm
and you will see files/folders mentioned above in new hlp folder. You can then check it’s contents to be familiar with the structure.
Let’s build the structure
Now you need to create four files.
- .OPF file – Open Packaging Format file is XML based file and defines “structure” of your final book. More can be found is OPF specification. In Sample folder of KindleGen installation you can check Guide.opf file as it contains also comments to all elements. I used it as a starting point also. Biggest advantage is that it contains information about mandatory elements of the file. Most important elements are: <manifest> – contains list of all files you’ll include in final book. <spine> – ordered (in a way of linear reading) list of HTML files. <metadata> – some general info about final book. As I included cover, it has to be mentioned here.
- .NCX file – Navigation Control file for XML is table of contents. In Sample folder you’ll see some nesting of elements but I found that for my case I was OK with just two levels – chapter and section. On my Kindle I don’t care about five levels as it’s in source CHM file.
- toc.html – Table of contents as it will be shown visually on Kindle. I just used simple <ul> tags to show really basic outline. You can see the picture of my TOC below.
- about.html – some short info about the book.
Two mentioned HTML files are not mandatory but I found useful to include them.
As a resume let’s see what we have now.
Dropbox:\PowerShell\Scripts\Help2Kindle\PowerShellP> ls
Mode LastWriteTime Length Name
—- ————- —— —-
d—- 4.1.11 10:27 PM html
d—- 3.1.11 8:19 PM local
-a— 4.1.11 11:57 AM 810 about.html
-a— 5.1.11 4:41 PM 6616 cover.gif
-a— 4.1.11 5:19 PM 109400 PowerShellP.ncx
-a— 5.1.11 4:49 PM 94708 PowerShellP.opf
-a— 4.1.11 4:58 PM 44067 toc.html
We can go to generate our final MOBI file.
Dropbox:\PowerShell\Scripts\Help2Kindle\PowerShellP> kg .\PowerShellP.opf
***********************************************
* Amazon.com kindlegen(Windows) V1.1 build 99 *
* A command line e-book compiler *
* Copyright Amazon.com 2010 *
***********************************************
opt version: try to minimize (default)
Info(prcgen): Added metadata dc:Title “PowerShell Help”
Info(prcgen): Added metadata dc:Date “2011-01-05″
Info(prcgen): Added metadata dc:Creator “@makovec”
Info(prcgen): Added metadata dc:Publisher “PowerShell.cz”
Info(prcgen): Added metadata dc:Subject “PowerShell Help”
Info(prcgen): Added metadata dc:Description “Help files provided with PowerShell.”
Info(prcgen): Parsing files 0000457
Info(prcgen): Resolving hyperlinks
Info(prcgen): Resolving start reading location
Info(prcgen): Added metadata Start reading “65BE”
Info(prcgen): Building table of content URL: Dropbox:\PowerShell\Scripts\Help2Kindle\PowerShellP\PowerShellP.ncx
Info(prcgen): Computing UNICODE ranges used in the book
Info(prcgen): Found UNICODE range: Basic Latin [20..7E]
Info(prcgen): Found UNICODE range: Letter-like Symbols [2100..214F]
Info(prcgen): Found UNICODE range: General Punctuation – Windows 1252 [2026..2026]
Info(prcgen): Found UNICODE range: Latin-1 Supplement [A0..FF]
Info(prcgen): Building MOBI file, record count: 0001927
Info(prcgen): Compiling HTML Parser restart information
Info(prcgen): Final stats – text compressed to (in % of original size): 038.49%
Info(prcgen): The document identifier is: “PowerShell_Help”
Info(prcgen): The file format version is V6
Info(prcgen): Saving MOBI file
Info(prcgen): MOBI File successfully generated!
And the file is there:
Dropbox:\PowerShell\Scripts\Help2Kindle\PowerShellP> ls *.mobi
Mode LastWriteTime Length Name
—- ————- —— —-
-a— 6.1.11 11:59 PM 4598551 PowerShellP.mobi
Some notes
- I manually modified some HTML files. There were missing some tag’s parameters and KindleGen generated warning. It was not critical but I wanted nice output just with info messages.
- I found out that (in my opinion) is not necessary to create chapters/sections with deep nesting. Maybe – as the only exception – is the toc.html file itself where it should be useful to see the structure. But I decided that it’s not that important for me.
- You need to put ordered file names into <spin> tag in OPF file. Otherwise you content will be sorted by file name which is not so useful.
- Use Sample folder as a starting point. It’s pretty well described.
- Cover image size should be 800×600.
That’s enough of theory for now. Today we didn’t play with PowerShell so much so next time I’ll dedicate whole post to techniques I used during whole process (generating of files, TOC order, etc.)
PowerShell help on Kindle
Jan 5th
I bought my Kindle few months ago. As I am reading whole day during every possible break I have the Kindle still with me. Last week I was checking new book from Don Jones – Learn Windows PowerShell in a Month of Lunches. As I ordered it in MEAP I like Kindle feature to add my own notes “directly” to the book. What I am missing – is PowerShell
So I decided to move help files to Kindle to be able check anything I need.
Amazon provides command line tool named KindleGen and I recommend you to download it together with Kindle Previewer (both located at Amazon Kindle’s Publishing Program page). With KindleGen you’ll receive also two samples so you can find how it works.
As a source I used CHM file provided with PowerShell (you can access it from ISE by pressing F1 key).
I simply extracted HTML files from that file (by running hh.exe –decompile hlp c:\windows\help\WindowsPowerShellHelp.chm) and then process those files with KindleGen. As a result I received nice MOBI file
You can download the result from this location.
As this was really funny work and whole process of the file creation is not described anywhere (AFAIK) I plan some blog posts in the near future to show how to work with KindleGen itself and also to show how I used PowerShell to help me with some conversion/copy&pasting/generating XHTML.
Please note that my influence to this book was only the conversion from CHM to MOBI. All the texts inside were created by people from PowerShell team (except to Preface – I added it just for clarification) and all kudos needs to be send to Redmond.
