Wednesday, December 30, 2015

Primary Devices of a User Collection

One of the limitations (to my knowledge) within ConfigMgr's console, is the ability to cross views easily in reference to the SQL DB.

I've wanted, for awhile, to be able to take a User Collection, and generate a Device Collection that are the Primary devices those users are set to.

This is what I've come up with so far as a new view in SQL.

001
002
003
004
005
006
SELECT dbo.vCollectionMembers.SiteID AS CollectionID, dbo.vCollectionMembers.Name AS Username, dbo.v_R_System_Valid.Netbios_Name0 AS DeviceName
FROM   dbo.v_UsersPrimaryMachines INNER JOIN
       dbo.v_R_User ON dbo.v_UsersPrimaryMachines.UserResourceID = dbo.v_R_User.ResourceID INNER JOIN
       dbo.v_R_System_Valid ON dbo.v_UsersPrimaryMachines.MachineID = dbo.v_R_System_Valid.ResourceID RIGHT OUTER JOIN
       dbo.vCollectionMembers ON dbo.v_R_User.Name0 = dbo.vCollectionMembers.Name
WHERE  (dbo.vCollectionMembers.SiteID = N'XYZ123456'AND (dbo.vCollectionMembers.ArchitectureKey = '4')

I also popped this together, a list of all users, and their assigned Primary Devices.

001
002
003
004
005
SELECT dbo.v_R_User.Unique_User_Name0 AS [User], dbo.v_R_System_Valid.AD_Site_Name0, dbo.v_R_System_Valid.Netbios_Name0 AS ComputerName
FROM   dbo.v_UsersPrimaryMachines INNER JOIN
       dbo.v_R_User ON dbo.v_UsersPrimaryMachines.UserResourceID = dbo.v_R_User.ResourceID RIGHT OUTER JOIN
       dbo.v_R_System_Valid ON dbo.v_UsersPrimaryMachines.MachineID = dbo.v_R_System_Valid.ResourceID
WHERE  (dbo.v_R_User.Unique_User_Name0 <> N'NULL')

Next, I'll put together a little PowerShell function to get this list of Devices, and then place them in a Collection.

Fancy!

Now I fully expect to be shown how this is something do-able in one or two simple clicks within the console. :)

Friday, November 13, 2015

Using PowerShell to Query a Site Server

Quick little example of how to use Get-WMIObject to pull data from the Primary (or CAS?).

This is useful since sometimes it's very easy to put together a query in SQL Managlement Studio, the WQL translates very closely.

Enjoy!

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
FUNCTION Get-SCCMSqlQuery
{
    Param([parameter(Mandatory=$true)]$ApplicationName,$CollectionName,$SiteName,$SCCMPrimary)
        $SCCMWMI="root\sms\site_$SiteName"
    Get-WmiObject -namespace $SCCMWMI -computer $SCCMPrimary -query "SELECT AssignmentID,
    Assignment_UniqueID,CollectionID,CollectionName,ApplicationName FROM SMS_ApplicationAssignment
    WHERE ApplicationName LIKE '$ApplicationName' AND CollectionName
    LIKE '$CollectionName'"
 | Select-Object ApplicationName,CollectionName,AssignmentID
}

$App = 'TestApp'
$Collection = 'TestCollection'

#Example Returns
 Get-SCCMSqlQuery -ApplicationName $App -CollectionName $Collection -SiteName 'XYZ' -SCCMPrimary 'contoso.website.com'
(Get-SCCMSqlQuery -ApplicationName $App -CollectionName $Collection -SiteName 'XYZ' -SCCMPrimary 'contoso.website.com').AssignmentId

Thursday, October 8, 2015

ISESteroids Version Control

I have little background in programming, code development and best practices.

My skillset in PowerShell developed by playing continually and trying to cover a need.

As a result I have a large folder of trials, tests, failures, terribly filed notes about tests. 

Incredibly painful to use and navigate.

I bet quite a few people have this same thing going on.

Any programmers that look at it probably feel this way:


This directory content will probably look familiar to some folks!


DoSomethingTest.ps1
DoSomethingTest2.2.ps1
DoSomething1.2.ps1
DoSomethingDraftTest.ps1
DoSomethingDraftTestnotes12.txt
Trythis.ps1
Something Do This To A Thing.ps1

So, a folder of junk.


Versioning was a cool idea.

A concept that I never was taught and I never stopped and took the time to learn.

Terrible habits I developed

Deliberately not saving due to just wanting to test something out.
That whole File>SaveAs was just too much work.
Rambling non-descriptive file names.

Installing the trial of ISESteroids changed everything!


Totally a game changer for me.

After a few hours of clicking around I stumbled on...

“Toggle Script File Versioning Management Tool”


I’m sorry….whats this!?


I saved a script and started playing.

Here’s how it works.


Your initial save does not do anything in this pane.


Check off “Auto Mode” for now and change something in your script.


Hit F5 to run the script.


Let’s change another thing, and make another save.


Oh my goodness! What’s this! I’ll go through what I understand.


Automatic


Upper right, this version was created via auto mode (I’ll get to manual in a moment).

Delete


Deletes this version.

Notes


You can add your own notes to the version, these notes are not saved in the script itself.

Open


This opens the version in a new tab within ISE so you don’t lose what you were actually working on. Note that you need to resave this version as a new file and turn versioning on if you want to sort of branch off.


Compare


Big one here!

Click Compare on Version .01 and you jump right into WinMerge!

This isn’t a guide on how to use WinMerge, but it’s pretty intuitive.

If you make a change, go to File > Save and you can commit your change.



Then when you close WinMerge, PowerShell_ISE will prompt you to reload the file.



Note that by reopening the file in this way, it’s now your current saved version.

It doesn’t create another minor version.

Another note. If you have WinMerge open, you cannot interact with the ISE window until it’s closed.
I thought that ISE had gone wonky and I was going to kill the program before it dawned on me.

Version  Development Status




Click on "Unclassified" and you’ll be able to chose the point at which the version is in development. 

Alpha > Beta > Stable > Internal > Release

Please know that is the sequence in which I see development is ordered, that may not be correct.

Manual Mode?



So, we’ve used Auto Mode, and understand what we’re seeing up there in the pane.

Let’s look at manually adding a version.

Click “Add New Version”


Add New Major Version


Notice how all our saves are small increases? (0.1 -> 0.2 -> 0.3)

This will increment the version in full versions (1.x -> 2.x -> 3.x).

Add New Minor Version


Similar to major version, this just adds a non automatic (non triggered by saving) minor version.

I also noticed if you bump past Version 0.9 it neatly goes to Version 0.10, 0.11 and so on.

Very nice! Another neat thing is that if you delete versions along the way, it remembers what they were, and doesn’t try to “fill in the blank” when you make a new version.


Add New Major Version and Remove Minor Versions


This creates a new V x.00 and deletes ALL the 0.xx versions listed.

You can click cancel and it will just create the Major Version.


Open History Archive in Explorer


Now you can discover how ISESteriods is actually performing all this magic!

Hit this and you will be pointed to a ZIP file sitting in the same folder as your saved (current) PS1.

Opening and extracting this zip will give all the versions you have created as plain old PS1 files.

The “index” file is an XML and with all the creation time, username and notes you created!

Side note! If you want a visual tool to help play with XPath searches check out:

(and note that the menu is hidden, hit Alt and you can add an XML to work with)

Final Words


If I possessed a bit of forethought, I would have looked and seen this blog entry was more concisely written in these locations:

Regardless, go get ISESteriods today and start trying it out! 10 day trial is worth it.

Wednesday, August 19, 2015

E-Mail when OSD Complete

We had a requirement to allow HelpDesk Analysts to image computers, and try to keep it fairly secure, and also let a group of people know, via email that the deployment had completed successfully.

Since at least SCCM 2007, there have been scripts triggered off of Status messages to perform this task.

These are the excellent guides I followed to get this up and running. The main two are these here, blended together.

  • http://www.systemcenterdudes.com/sccm-osd-send-email/
  • http://servertechs.info/remove-client-from-collection-after-successful-sccm-task-sequence-powershell/

The environment is SCCM 2012r2 CU4 running on Windows Server 2012r2.

Outside of SCCM

Create a Service Account in AD - "SVC-SCCM-RemovalAdmin"


On your Primary Server, in Windows

Add the Service Account to the Primary Site Server Local User Group "Remote Management Users"

We will presume you keep your scripts in this location on the Primary Server: C:\Scripts

Put these two files there (Ryan Norton @ ServerTechs.info had these on his page).

CallScriptAsOtherUser.ps1
    • Edit Username
    • Edit Script Paths!

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
[CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True)][string[]]$Collections,
        [Parameter(Mandatory=$True)][string]$SiteCode,
        [Parameter(Mandatory=$True)][string]$ClientName
         )
##############################################
#Enter the username of the service account below: (domain\username)

$Username = "DOMAIN\SVC-SCCM-RemovalAdmin"

#Enter the location of the script below
$ScriptPath = "C:\Scripts\CollectionRemovalScript\RemoveCollectionRule.ps1"

#Enter the location of the credentials file below
$CredFile = "C:\Scripts\CollectionRemovalScript\cred.txt"

##############################################

foreach ($Collection in $Collections)
{
if ($CSCollections.length -gt 0)
{
$CSCollections = $CSCollections + "," + $Collection
}
else
{
$CSCollections = $Collection
}
}

$Arguments = "-Collections " + $CSCollections + " -SiteCode " + $SiteCode + " -ClientName " + $ClientName

$Password = Get-Content $CredFile | ConvertTo-SecureString

$Credentials = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $Username,$Password

$cmd = "powershell.exe -ExecutionPolicy ByPass -Command " + $scriptPath + " -collections " + $CSCollections + " -Sitecode " + $SiteCode + " -Clientname " + $ClientName
$scriptblock = $ExecutionContext.InvokeCommand.NewScriptBlock( $cmd )

Invoke-Command -ComputerName . -ScriptBlock $ScriptBlock -Credential $Credentials



RemoveCollectionRule.ps1
    • Make sure the path to your module is correct (aka where you have SCCM itself installed locally).
    • In Send-MailMessage: Change the From, modify Subject, Recipients, and your own mail server.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
#Created By Ryan Norton 21/04/2014

#Intended to be used as part of a status filter rule in SCCM 2012 (sp1 & later) to remove a client from a collection once a task sequence has completed successfully

#The script will check one or more collections to see if the specified client exists and remove it from each.

#Example usage:
#The following example will connect to site ABC, check the collections named BuildWin7SP1 & BuildWin8 for a client called TestClient1 and remove the direct membership rule if found.
#C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass \fileName.ps1 -Collections BuildWin7SP1,BuildWin8 -SiteCode ABC -ClientName TestClient1

[CmdletBinding()]
    Param(
        [Parameter(Mandatory=$True)][string[]]$Collections,
        [Parameter(Mandatory=$True)][string]$SiteCode,
        [Parameter(Mandatory=$True)][string]$ClientName
         )

Import-Module "c:\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1"

cd $SiteCode":"

foreach ($Collection in $Collections)
{
    Get-CMDevice -CollectionName $Collection | select Name | ForEach-Object `
    {
            if ( $_.name -eq $ClientName)
            {
            Remove-CMDeviceCollectionDirectMembershipRule -CollectionName $Collection -ResourceName $ClientName -Force
    Send-MailMessage -From OSDActuallyCompleted@domain.com -Subject "Unit $ClientName has completed imaging and has been removed from the AnalystImaging Collection." -To Important@domain.com, AlmostAsImportant@domain.com -Body "Unit $ClientName has completed imaging and has been removed from the HelpDeskImaging Collection." -Cc someone@domain.com -SmtpServer your.server.com
            }
    }
}

I bet you saw the reference up there to the cred.txt file. You need to create that unless you like being terrible at securing anything. This is really just ripped right from Ryan Norton's page:

Make sure you have PSExec on the system.

  1. Open a Administrative Command Prompt.
  2. Run: "PSExec -i -s powershell.exe"
  3. Enter: "Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File C:\Scripts\Cred.txt"
  4. Nothing will prompt you. Type the password for the Domain\SVC-SCCM-RemovalAdmin. Followed by the enter key. Very. Carefully.

Now time to jump into the SCCM Console.

Create a Collection you want to be the place people put Devices to be imaged (HelpDeskImaging).

Give the users the rights and scope to add devices to that collection. This is where you can use limiting collections to then prevent certain servers from being moved into this collection to be re-imaged. Deny beats Allow!

Create a new Security role by going to, Administration Tab - Security - Security Role. You can call it "CollectionEditor" giving only Collection Read and Modify rights. Chose appropriate Security Scope to include the Collection you want to work with (HelpDeskImaging).


Jump over to the Administration Tab - Security - Administrative Users and add the "SVC-SCCM-RemovalAdmin" account there, giving it the "Collection Editor" role. Assign security scope here if needed.


So now we have the account added, it can now actually remove members from the Collection (kinda). But it needs to be triggered.

Head to Administrative Tab - Sites - Highlight your Site - Status Filter Rules.


Task Sequence Manager must be typed. It isn't on the drop down. Message ID must be 11171.

You can specify a Property + Property ID to have this only trigger for certain OSD deployments in that collection. Leave it blank to trigger on all completions within that collection(s).

Under Actions you'll need "Run a Program" filled out. Remember to use the right path for the script.

001
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy ByPass -File c:\Scripts\CollectionRemovalScript\CallScriptAsOtherUser.ps1 -Collections HelpDeskImaging -SiteCode %msgsc -ClientName %msgsys

So keep in mind, the script is going to run on any completion which is triggered either for any completion (just Message ID), or specific ones (If you added Property + Value).

What changes the script sending the email or not is how you have Collections chosen in the "Run A Program" Action within the Status Message.



That's it! You should be getting alerts!

There are some nice troubleshooting links in the two links I posted above, and plenty more below.

Here are a bunch more for inspiration, troubleshooting and other methods!

http://www.systemcenterdudes.com/239/
http://www.petervanderwoude.nl/post/tweeting-the-deployment-status-of-a-system-via-orchestrator-and-configmgr-2012/
https://anothermike2.wordpress.com/2014/06/13/osdusing-ztisendmail-to-send-email-in-a-ltizti-task-sequence/
https://sccmfaq.wordpress.com/2013/05/20/sccm-2012-sp1-remove-client-from-collection-after-osd/
http://scug.be/sccm/2010/10/01/configmgr-osd-task-sequence-success-or-failure-notification/
http://servertechs.info/remove-client-from-collection-after-successful-sccm-task-sequence-powershell/

Wednesday, April 1, 2015

Ever wish you could just link to an install in the AppCatalog?



In System Center Config Manager 2012r2 there isn't a built in way to send someone directly to a specific application (that I have ever seen reference to).

If you search the web, you'll find reference to deep linking, but only really in reference to the Windows 8.x Store (sideloaded apps).

Not the actual Application Catalog hosted internally.

This leads to:

Ever tell someone to go to the Application Catalog and install something?

Ever notice that some people find it hard to get to exactly what you want them to?

So let's just shoot them a direct link!

Go take a read right here:

Here's the code found on that Nickal Miron's Blog
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
#Dictionary values for encoding/decoding
$codes = @{}
$codes.Add('/', '2F00')
$codes.Add('_', '5F00')
$codes.Add('-', '2D00')
$codes.Add('0', '3000')
$codes.Add('1', '3100')
$codes.Add('2', '3200')
$codes.Add('3', '3300')
$codes.Add('4', '3400')
$codes.Add('5', '3500')
$codes.Add('6', '3600')
$codes.Add('7', '3700')
$codes.Add('8', '3800')
$codes.Add('9', '3900')
$codes.Add('a', '6100')
$codes.Add('b', '6200')
$codes.Add('c', '6300')
$codes.Add('d', '6400')
$codes.Add('e', '6500')
$codes.Add('f', '6600')
$codes.Add('o', '6F00')
$codes.Add('p', '7000')
$codes.Add('i', '6900')
$codes.Add('l', '6C00')
$codes.Add('n', '6E00')
$codes.Add('t', '7400')

$upperCodes = @{}

$upperCodes.Add('A', '4100')
$upperCodes.Add('B', '4200')
$upperCodes.Add('C', '4300')
$upperCodes.Add('D', '4400')
$upperCodes.Add('E', '4500')
$upperCodes.Add('F', '4600')
$upperCodes.Add('I', '4900')
$upperCodes.Add('S', '5300')


function encode([string]$AppID)

{
    $encoded = $null
    #foreach char in string, get encoded value from dictionary
    foreach ($char in [Char[]]$AppID)
    {
        #if char is uppercase look in uppercase dictionary
        if ([char]::IsUpper($char)) { $encoded = $encoded + $upperCodes.Get_Item($char.ToString()) }
        else { $encoded = $encoded + $codes.Get_Item($char.ToString()) }
    }
    return $encoded
}

function lookUpKey($code)

{
    $returnVal = $null
    #look in lowercase dictionary for code
    if ($codes.ContainsValue($code))
    {
        foreach ($key in ($codes.GetEnumerator() | Where-Object {$_.Value -eq $code}))
        {
            $returnVal = $key.Name
        }
    }
    #if not there look in uppercase dictionary
    elseif ($upperCodes.ContainsValue($code))
    {
        foreach ($key in ($upperCodes.GetEnumerator() | Where-Object {$_.Value -eq $code}))
        {
            $returnVal = $key.Name
        }
    }
    return $returnVal
}

function decode([string]$AppIDSig)

{
    $i = 0
    $Code = $null
    $decoded = $null

    foreach ($char in [Char[]]$AppIDSig)

    {
        #if less then 4 characters in code grab next character
        if ($i -le 3)
        {
            #build 4 char codes
            $code = $code + $char
            $i++
        }
        #if 4 characters are in code lookup the decoded value
        else
        {
            $decoded = $decoded + (lookUpKey $code)
        
            #reset code and start building 4 char code again
            $code = $char
            $i = 1
        }
    }
    #foreach statement does not iterate the last time to get last code, look up value for last code here
    $decoded = $decoded + (lookUpKey $code)

    return $decoded

}


That code is pretty darn awesome.

But the page lacks instructions on what a simple person like I should ACTUALLY do with it.

Step 1 - Go get an AppID!

  1. Open SCCM Console, then Software Library, Application Management, Applications. Right click the top header bar of your app's and check off "CI Unique ID".
  2. Select the Application you want, hit CTRL-C (don't right click and hit copy, then you're copying the application).
  3. Open Notepad, hit CTRL-V.
  4. Delete all the stuff before ScopeId_...
  5. Delete the trailing /## (This is the revision number of the Application. You cannot refer to a previous version of what you have currently deployed.)

Step 2 - Encode it!

  1. Paste that script into Powershell_ISE.
  2. I'm lazy, and I just throw at the end of the script "Encode ScopeID.../Application_..."
  3. Run the script.
  4. You get a massively long number.

Step 3 - I'm done right?

  1. Let's pretend this is your ApplicationCatalog link (that you'd click on in SoftwareCenter, or you wisely published to all desktops with a GPO).
  2. Cool! BingoBango throw that long number bit on the end and we're done right? Nope.
  3. That link shows your Library. We want an item from the Catalog.
  4. Adapt your link to look like the below:


That's it.

Now tell me how to do it better!

It would be pretty easy to just use PS to pull all the AppID's and spit you out ALL you links nice and neatly titled in a CSV.

Wonder if this ends up in the next iteration of Coretech's Application Approval Tool ( http://blog.coretech.dk/kea/coretech-application-e-mail-approval-tool/) ?