Friday, June 10, 2016

PowerShell & SCCM 2012: Portable Script for Adding Computers to Collections

Being a field technician, the SCCM Console is not always nearby. The script in this post works from a thumb drive and has no need for the SCCM PowerShell module. Run this script with SCCM level credentials or you can add your username/password ($admcred) and add -credential $admcred to your SCCM WMI calls.


$SCCMserver = "SCCMServer01"
$namespace = "root\sms\site_xx1"
$computername = read-host "Enter Computer Name"
$i = 0

#SCCM
#Getting list of current software collections and then filtering out unneeded selection items
if (test-connection $SCCMServer){
$xlist = import-csv (\\server\share\offlist.csv #list of filtered out collections
$xlist|add-member -notepropertyname line -notepropertyvalue ""
$CollectionList = get-wmiobject -query "Select * from SMS_Collection" -namespace $namespace -computername $SCCMserver
$CollectionList = $CollectionList|? {$xlist.name -notcontains $_.name}
foreach ($C in $CollectionList){$i++;$C|add-member -notepropertyname line -notepropertyvalue $i} #adding line numbers to each collection entry
}else{write-host "No SCCM Server connection, ending script" -f yellow;start-sleep 10;exit}

$CurrentApps = Get-WmiObject -Namespace $namespace -Class SMS_fullcollectionmembership -ComputerName $SCCMServer -filter "Name = '$computername'"
$InstalledSoftwareList = $CollectionList|? {$CurrentApps.collectionID -contains $_.collectionID}

do{
cls
write-host "Available SCCM Software Packages" -f white
$collectionlist|? {$InstalledSoftwareList.name -notcontains $_.name}|format-table line,name
write-host "SCCM Software already installed on $computername" -f white
$InstalledSoftwareList|format-table line,name -hidetableheaders
if ($newsoftware){
write-host "Software to be added" -f yellow
$newsoftware|format-table line,name -hidetableheaders
}
write-host "Enter " -nonewline;write-host "line number" -f white -nonewline;write-host " to add SCCM software to $computername"
write-host "Enter " -nonewline;write-host "DONE" -f white -nonewline;write-host " if satisfied with install list"
$addpackages = read-host "(Line number or Done)"
if ($addpackages -ne "done"){
$newsoftware += $collectionlist|? line -eq $addpackages
}
} while ($addpackages -ne "done")

Here's a sample of this menu after selecting line item 7 and typing DONE at the prompt:
Available SCCM Software Packages

line name
---- ----
   1 Adobe Acrobat Pro DC
   3 Adobe Acrobat Standard DC
   4 Base Camp
   5 Google Earth Pro

SCCM Software already installed on PCReception01

   2 Adobe Acrobat Reader DC
   6 7 Zip

Software to be added

7 Windows Mobile Device Center

Enter line number to add SCCM software to PCReception01
Enter DONE if satisfied with installation list
(Line number or DONE): DONE

Now we add $computername to the selected collection via the $newsoftware variable. Let's create a new rule that adds the computer it to the collection:
foreach ($c in $newsoftware){
$ID = $c.collectionID
$Collection = get-wmiobject -class SMS_Collection -namespace $namespace -computername $SCCMserver|? CollectionID -eq $ID
$ruleclass = get-wmiobject -namespace $namespace -class "SMS_CollectionRuleDirect" -computername $SCCMserver -list
$AddComputer = get-wmiobject -ComputerName $SCCMServer -Namespace $Namespace -Class "SMS_R_System" -Filter "Name = '$($computername)'"
$NewRule = $RuleClass.CreateInstance()     
$NewRule.RuleName = $($AddComputer.name)
$NewRule.ResourceClassName = "SMS_R_System" 
$NewRule.ResourceID = $($AddComputer.resourceid)
$Collection.AddMembershipRules($newrule)
$Collection.requestrefresh()
}

And here's how that looks:
___GENUS               : 2
___CLASS               : ___PARAMETERS
___SUPERCLASS          :
___DYNASTY             : ___PARAMETERS
___RELPATH             :
___PROPERTY_COUNT      : 2
___DERIVATION          : {}
___SERVER              :
___NAMESPACE           :
___PATH                :
QueryIDs               : {0}
ReturnValue            : 0
PSComputerName         :

___GENUS               : 2
___CLASS               : ___PARAMETERS
___SUPERCLASS          :
___DYNASTY             : ___PARAMETERS
___RELPATH             :
___PROPERTY_COUNT      : 1
___DERIVATION          : {}
___SERVER              :
___NAMESPACE           :
___PATH                :
ReturnValue            : 0
PSComputerName         :


You want the returnvalue to equal 0.  This means the rule successfully added your computer to the collection.

Sunday, May 22, 2016

PowerShell & SCCM 2012: Get Old Computer SCCM Software Packages, add more software, and Export CSV out for New Computer

Using PowerShell and WMI to access the SCCM server, this post shows how to transfer a software list from the old computer and export it for the new one. In another post I describe how to automate the new computers software package collection addition process after its registered itself with the SCCM server and installed the SCCM client software.

$SCCMserver = SCCMServer01
$namespace = "root\sms\site_xx1"
$adminname = "Domain\AdmUser"
$password = get-content .\adminpassword.txt|convertto-securestring -key (1..16)
$admcred = new-object system.management.automation.pscredential($adminname,$password)
$i = 0

$OldComputerSoftware = Get-WmiObject -Namespace $namespace -Class SMS_fullcollectionmembership -ComputerName $SCCMServer -filter "Name = '$SCCMoldcomputer'" -credential $admcred
$OldComputerCollectionMemberList = $CollectionList|? {$OldComputerSoftware.collectionID -contains $_.collectionID}

Above I declare my variables, query the SCCM server for all the software on the old computer, and compare it to the $CollectionList collections filtering array. Next I create $NewPCSoftwareList using the old computer software list. Add line numbers and filter unwanted collections via $XList. The $AddedSoftware variable displays the existing software from the old computer and will be updated with software selections in the read-host menu below:

$NewPCSoftwareList = $OldComputerCollectionMemberList|select Name,CollectionID
foreach ($C in $NewPCSoftwareList){$i++;$C|add-member -notepropertyname line -notepropertyvalue $i}
$NewPCSoftwareList = $NewPCSoftwareList|? {$xlist.name -notcontains $_.name}
$addedsoftware = $collectionlist|? {$NewPCSoftwareList.name -contains $_.name}


Available SCCM Software Packages
 1 Adobe Acrobat Pro
 2 Adobe Acrobat Standard
 3 Base Camp
 5 Topography Software
 6 Google Earth Pro
 7 Windows Mobile Device Center

OldComputerName SCCM Software will be installed on NewComputerName 

 4 7-Zip
 8 Microsoft Office 2013

Enter line number to add SCCM software to NewComputerName
Enter DONE if satisfied with install list
(Line number or Done): _


do{
cls
write-host "Available SCCM Software Packages" -f white
$collectionlist|? {$InstalledSoftwareList.name -notcontains $_.name}|format-table line,name
write-host "SCCM Software already installed on $computername" -f white
$InstalledSoftwareList|format-table line,name -hidetableheaders
if ($newsoftware){
write-host "Software to be added" -f yellow
$newsoftware|format-table line,name -hidetableheaders

}
write-host "Enter " -nonewline;write-host "line number" -f white -nonewline;write-host " to add SCCM software to $computername"
write-host "Enter " -nonewline;write-host "DONE" -f white -nonewline;write-host " if satisfied with install list"
$addpackages = read-host "(Line number or Done)"
if ($addpackages -ne "done"){
$newsoftware += $collectionlist|? line -eq $addpackages
}
} while ($addpackages -ne "done")
The starting menu displays the full available software list minus those software already pre-selected to be added to the new computer. I input the number for each additional software I want added to the new computer then type DONE when finished adding software. Note that the line numbers disappear from the top list when selected on the bottom one. After DONE is typed, the $addedsoftware array gets exported to a CSV that the new computer will access in order to install that software. That part of the process will be in another post.

if ($addedsoftware){$addedsoftware|export-csv .\NewComputerName-SCCMsoftware.csv}

PowerShell & SCCM 2012: Retrieving and Filtering Collections

In order to expedite my computer replacement process, I decided to automate the software transfer from the old computer to the new one. This post goes over the first part of that process. I thank the many great people that have posted how to retrieve information from SCCM via WMI. I built upon their success and customized it for my needs. In this first snippet, I declare my variables, poll the SCCM server, then query the SCCM server for all its collections ($CollectionList). I also import my collection filtering CSV file ($xlist).

$SCCMserver = SCCMServer01
$namespace = "root\sms\site_xx1"
$adminname = "Domain\AdmUser"
$password = get-content .\adminpassword.txt|convertto-securestring -key (1..16)
$admcred = new-object system.management.automation.pscredential($adminname,$password)
$i = 0

if (test-connection $SCCMServer){
$xlist = import-csv \\server\share\SCCMscripts\CollectionFilter.csv
$CollectionList = get-wmiobject -query "Select * from SMS_Collection" -namespace $namespace -computername $SCCMserver -credential $admcred
$CollectionList = $CollectionList|? {$xlist.name -notcontains $_.name}
foreach ($C in $CollectionList){$i++;$C|add-member -notepropertyname line -notepropertyvalue $i}
}

With the above, I've filtered through the complete collection list using my collection filtering CSV, added a new array property named line, and have run the resulting list through a line numbering loop so I can present the collection list for further filtering as in the example below.

Available SCCM Software Packages
 1 Adobe Acrobat Pro
 2 Adobe Acrobat Standard
 3 7 Zip
 4 Google Earth
 5 AutoCAD LT

Enter software package line number to remove from availability list
Enter NONE if no changes are needed
(Line number or NONE:): 5
Available SCCM Software Packages - Updated
 1 Adobe Acrobat Pro
 2 Adobe Acrobat Standard
 3 7 Zip
 4 Google Earth

Enter software package line number to remove from availability list
Enter NONE if no changes are needed
(Line number or NONE:): NONE

In the above example, my filtered $CollectionList displayed five software packages. I typed in number 5 to remove AutoCAD LT from my software list, press Enter, and the list now only has the four remaining packages. Once finished updating the software list, I will use the filtered list to apply against the old computer's software list. This helps me select software for its replacement without wading through all the other arbitrary collections and packages. I'll show that software selection process in another post.

do{
if (!($remove)){write-host "Available SCCM Software Packages" -f white}
if ($remove){write-host "Available SCCM Software Packages" -f white -nonewline;write-host " - Updated" -f green}
$CollectionList|format-table line,name -hidetableheaders
write-host "Enter software package " -nonewline;write-host "line number" -f white -nonewline;write-host " to remove from availability list"
write-host "Enter " -nonewline;write-host "NONE" -f white -nonewline;write-host " if no changes are needed"
$addtoxlist = read-host "(Line number or NONE)"
if ($addtoxlist -ne "none"){
[array]$remove += $CollectionList|? line -eq $addtoxlist|select name,collectionID
$CollectionList = $CollectionList|? {$remove.name -notcontains $_.name}
}
} while ($addtoxlist -ne "none")

After "None" is typed, the $remove list is applied to $xlist then exported back out to the filter list.

if ($remove){
$xlist = $xlist|select Name,CollectionID #removing the line number object
$xlist += $remove|select Name,CollectionID #adding the selected collection items to the filter list
$xlist = $xlist|sort CollectionID -unique #removing duplicate entries
$xlist|export-csv \\server\share\SCCMscripts\CollectionFilter.csv}

Thursday, January 7, 2016

PowerShell: Funny Tech Support Excuse Website

Jeff Ballard created a funny website titled "The Bastard Operator From Hell Style Excuse Server." We can use his funny tech support responses for our own purposes by extracting them from his site and adding them to our PowerShell script. Here's a snippet that'll create a string variable for your use:

$url = 'http://pages.cs.wisc.edu/~ballard/bofh/bofhserver.pl'
$excuse = ((Invoke-webrequest $url).parsedhtml.getelementsbytagname('font')|select -expandproperty outertext)[-1]

Here's a few $excuse samples:
  • asynchronous inode failure
  • Interference between the keyboard and the chair.
  • HTTPD Error 666 : BOFH was here
  • radiosity depletion
Now you'll have some great excuses to reply back with while you're working on the real problem.


Sunday, January 3, 2016

PowerShell: Download Reddit Photos for Windows Background and/or Screensaver

Microsoft Windows® has the ability to display photos for both your computer desktop background and screensaver.  I like to keep a fresh rotation of photos so I decided to write a dirty script to download highly upvoted photos, ensure they are in landscape orientation, and possess at least a 2048 pixel width.  I'll go through each part of the process.

Note: I mention "dirty" as not all photos will download correctly due to various URL's in each post and not all extraneous URL conditions haven't been cleaned up.  Since most Reddit posters use IMGUR for their photos, I focused on resolving those URLs.  Additionally, I write my code in Notepad, not IME, so it's in an old skool batch file type format.  You can edit it to your liking if your OCD is kicking in.

Let's get started (full script at bottom of this post):

#Pulling the JPG attributes requires a query of the extended file properties.  Here I'm calling the shell so we can query it later.

$shell = new-object -com shell.application

#Using the Reddit API and copying a sample search URL, I've created the JSON query to pull in the posts
#Note that I'm searching the Earthporn subreddit that are one month or less old, increasing my result list to one hundred objects, and each post has a net post score of at least 1500

$images = (invoke-restmethod "https://www.reddit.com/search.json?q=subreddit:earthporn&restrict_sr=&sort=relevance&t=month&limit=100").data.children.data|? score -ge 1500

#I noticed sometimes the Invoke-restmethod would get an SSL error so I check for that and run the command again if the $images count is zero.

if ($images.count -eq 0){$images = (invoke-restmethod "https://www.reddit.com/search.json?q=subreddit:earthporn&restrict_sr=&sort=relevance&t=month&limit=100").data.children.data|? score -ge 1500}

#Change $destination to your photos folder.  I put mine in my Dropbox subfolder so my other computer gets the new photos too.

$destination = "C:\Users\MyUserAccount\Dropbox\Earth"

#Creating a filename list so I don't re-download the same photos

$filecheck = gci $destination|select -expandproperty name

#Now that I loaded $Shell, created a Reddit $images result list for downloading, and pulled the list of existing photo filenames from $destination, I start my $images loop:

foreach ($i in $images){
#Cleaning up the URL and destination file name
$name = (($i.url).split('/')[-1]).replace('?1','')
if ($name -like "*.gif*"){continue}
if (($name -notlike "*.jpg") -and ($name -notlike "*.png")){$name = $name + ".jpg"}
if ($filecheck -contains $name){continue}
$fullname = (($destination + "\" + $name)).replace('?1','')
if ($i.url -like "*http://imgur.com*"){$i.url = ($i.url -replace ('http://imgur.com','https://i.imgur.com')) + ".jpg"}

#Downloading the JPG
if ($i.url -like "*://i.img*"){iwr ($i.url).replace('?1','') -outfile $fullname}
if ($i.url -like "*.staticflickr.com/*"){iwr ($i.url).replace('?1','') -outfile $fullname}
if (($i.url -like "*.jpg") -and ($i.url -notlike "*https://i.img*") -and ($i.url -notlike "*.staticflickr.com/*")){iwr ($i.url).replace('?1','') -outfile $fullname}
[array]$files += "URL: " + $i.url

#Pulling dimension property
$fileinfo = $shell.namespace($destination).parsename($name)
$dimension = $shell.namespace($destination).getdetailsof($fileinfo,31) -replace '[\W]',''

#If dimension field is bogus, replace with temp value so file can be deleted from destination
if ($dimension -notlike "*x*"){$dimension = "2047x1023"}

#Checking for landscape photo with horizontal size at least 2048 pixels
[array]$files += "Dimensions: " + $dimension
$horizontal = [int]$dimension.split('x')[0]
if ($horizontal -lt 2048){remove-item $fullname -force;continue}
$vertical = [int]$dimension.split('x')[-1]
if ($vertical -lt 1024){remove-item $fullname -force;continue}
if ($horizontal -lt ($vertical * 1.3)){remove-item $fullname -force;continue}

#If photo passes all checks, that file name is added to $files
[array]$files += "Success: " + $name
}

#End of loop, posting errors and results to $destination folder
$files|out-file ($destination + "\files.txt")
$error|out-file ($destination + "\errors.txt")


Next, save the script and add it to a Scheduled Task.  I used the following command in the task:
Program name: Powershell.exe
Arguments: -ExecutionPolicy Bypass c:\scripts\RedditBackgrounds.ps1 -windowstyle hidden

Now you'll have a scheduled task which automatically keeps your background and screensaver collection fresh.

Actual script:

$shell = new-object -com shell.application
$images = (irm "https://www.reddit.com/search.json?q=subreddit:earthporn&restrict_sr=&sort=relevance&t=month&limit=100").data.children.data|? score -ge 1500
if ($images.count -eq 0){$images = (irm "https://www.reddit.com/search.json?q=subreddit:earthporn&restrict_sr=&sort=relevance&t=month&limit=100").data.children.data|? score -ge 1500}
$DEarth = "C:\Users\MyuserName\Dropbox\Earth"
$destination = "c:\scripts\earth"
$filecheck = gci $DEarth|select -expandproperty name

foreach ($i in $images){
$name = (($i.url).split('/')[-1]).replace('?1','')
if ($name -like "*.gif*"){continue}
if (($name -notlike "*.jpg") -and ($name -notlike "*.png")){$name = $name + ".jpg"}
if ($filecheck -contains $name){continue}
$fullname = (($destination + "\" + $name)).replace('?1','')
if ($i.url -like "*http://imgur.com*"){$i.url = ($i.url -replace ('http://imgur.com','https://i.imgur.com')) + ".jpg"}
if ($i.url -like "*://i.img*"){iwr ($i.url).replace('?1','') -outfile $fullname}
if ($i.url -like "*.staticflickr.com/*"){iwr ($i.url).replace('?1','') -outfile $fullname}
if (($i.url -like "*.jpg") -and ($i.url -notlike "*https://i.img*") -and ($i.url -notlike "*.staticflickr.com/*")){iwr ($i.url).replace('?1','') -outfile $fullname}
[array]$files += "URL: " + $i.url
$fileinfo = $shell.namespace($destination).parsename($name)
$dimension = $shell.namespace($destination).getdetailsof($fileinfo,31) -replace '[\W]',''
if ($dimension -notlike "*x*"){$dimension = "2047x1023"}
[array]$files += "Dimensions: " + $dimension
$horizontal = [int]$dimension.split('x')[0]
if ($horizontal -lt 2048){remove-item $fullname -force;continue}
$vertical = [int]$dimension.split('x')[-1]
if ($vertical -lt 1024){remove-item $fullname -force;continue}
if ($horizontal -lt ($vertical * 1.3)){remove-item $fullname -force;continue}
[array]$files += "Success: " + $name
}
$files|out-file ($destination + "\files.txt")
$error|out-file ($destination + "\errors.txt")
move-item ($destination + "\*.*") $DEarth -force