The Collection

A collection of useful information.

Filtering by Category: PowerShell

PowerShell: Get Remote Desktop Services User Profile Path from AD.

Bit goofy this, when trying to get profile path information for a user you may think Get-ADUser will provide you anything you would likely see in the dialog in AD Users and Computers, but you would be wrong. Get-ADUser -Identity <username> -Properties * does not yield a 'terminalservicesprofilepath' attribute.

Instead you must do the following:

([ADSI]"LDAP://").terminalservicesprofilepath

You can use Get-ADUser to retrieve the DN for a user or any other method you prefer.

Citrix: Presentation Server 4.5, list applications and groups.

Quick script to connect to a 4.5 farm and pull a list of applications and associate them to the groups that control access to them. You will need to do a few things before this works:

Download the 4.5 SDK

If you are running this remotely you need to be in the "Distributed COM Users" group (Server 2k3) and will need to setup DCOM for impersonation (you can do this by running "Component Services" drilling down to the "local computer", right click and choose properties, clicking General properties and the third option should be set to Impersonate).

Finally you will need View rights to the Farm. If you are doing this remotely there is a VERY strong chance of failure is the account you are LOGGED IN AS is not a "View Only" or higher admin in Citrix. RunAs seems to be incredibly hit or miss, mostly miss.

$start = Get-Date
$farm = New-Object -ComObject MetaFrameCOM.MetaFrameFarm
$farm.Initialize(1)
$apps = New-Object 'object[,]' $farm.Applications.Count,2
$row = 0
[regex]$reg = "(?[^/]*)$"
foreach($i in $farm.Applications) {
	$i.LoadData($true)
	[string]$groups = ""
	$clean = $reg.Match($i.DistinguishedName).Captures.Value
	$apps[$row,0] = $clean
	foreach($j in $i.Groups) {
		
		if($groups.Length -lt 1){ $groups = $j.GroupName }else{ $groups = $groups+","+$j.GroupName }
	}
	$apps[$row,1] = $groups
	$row++
}

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$excel.DisplayAlerts = $false
$workbook = $excel.Workbooks.Add()
$sheet = $workbook.Worksheets.Item(1)
$sheet.Name = "Dashboard"
$range = $sheet.Range("A1",("B"+$farm.Applications.Count))
$range.Value2 = $apps
$(New-TimeSpan -Start $start -End (Get-Date)).TotalMinutes

App-V 5.0: PowerShell VE launcher.

Quick little script to enable you to launch local apps into a VE. Can be run of two ways:

Prompts the user for an App-V app and then the local executable to launch into the VE.

Accepts command line arguments to launch the specified exe into the specified VE.

Import-Module AppvClient
if($args.Count -ne 2) {
	$action = Read-Host "App-V app to launch into (type 'list' for a list of apps):"

	while($action -eq "list") {
		$apps = Get-AppvClientPackage
		foreach($i in $apps){ $i.Name }
		$action = Read-Host "App-V app to launch into (type 'list' for a list of apps):"
	}

	try {
		$apps = Get-AppvClientPackage $action
	}
	catch {
		Write-Host ("Failed to get App-V package with the following error: "+$_)
	}

	$strCmd = Read-Host "Local app to launch into VE:"

	try {
		Start-AppvVirtualProcess -AppvClientObject $app -FilePath $strCmd
	}
	catch {
		Write-Host ("Failed to launch VE with following error: "+$_)
	}
}else{
	$app = Get-AppvClientPackage $args[0]
	Start-AppvVirtualProcess -AppvClientObject $app -FilePath $args[1]
}

Usage:

  1. Prompt-mode: AppV-Launcher.ps1
  2. CMDLine Mode: AppV-Launcher.ps1 TortoiseHg C:\Windows\Notepad.exe

Note: The arguments are positional, so it must be Virtual App then Local Executable in that order otherwise it will fail. There is no try/catch on the CMDLine mode as it expects you to know what you are doing (and want as much information about what went wrong as possible) and there is no risk of damage.

PowerShell: Date Picker.

Quick function to prompt a user to select a date. Usage is pretty straighforward.

$var = $(DatePicker "<title>").ToShortDateString()
function DatePicker($title) {
	[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
	[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
	
	$global:date = $null
	$form = New-Object Windows.Forms.Form
	$form.Size = New-Object Drawing.Size(233,190)
	$form.StartPosition = "CenterScreen"
	$form.KeyPreview = $true
	$form.FormBorderStyle = "FixedSingle"
	$form.Text = $title
	$calendar = New-Object System.Windows.Forms.MonthCalendar
	$calendar.ShowTodayCircle = $false
	$calendar.MaxSelectionCount = 1
	$form.Controls.Add($calendar)
	$form.TopMost = $true
	
	$form.add_KeyDown({
		if($_.KeyCode -eq "Escape") {
			$global:date = $false
			$form.Close()
		}
	})
	
	$calendar.add_DateSelected({
		$global:date = $calendar.SelectionStart
		$form.Close()
	})
	
	[void]$form.add_Shown($form.Activate())
	[void]$form.ShowDialog()
	return $global:date
}

Write-Host (DatePicker "Start Date")

Citrix: Creating Reports.

A bit of a different gear here, but here are a couple examples, one using Citrix 4.5 (Resource Manager) andone using Citrix 6.0 (EdgeSight).

4.5
http://pastebin.com/r9752d43

6.0
http://pastebin.com/TFqRs6ew

$start = Get-Date

Import-Module ActiveDirectory
function SQL-Connect($server, $port, $db, $userName, $passWord, $query) {
	$conn = New-Object System.Data.SqlClient.SqlConnection
	$ctimeout = 30
	$qtimeout = 120
	$constring = "Server={0},{5};Database={1};Integrated Security=False;User ID={2};Password={3};Connect Timeout={4}" -f $server,$db,$userName,$passWord,$ctimeout,$port
	$conn.ConnectionString = $constring
	$conn.Open()
	$cmd = New-Object System.Data.SqlClient.SqlCommand($query, $conn)
	$cmd.CommandTimeout = $qtimeout
	$ds = New-Object System.Data.DataSet
	$da = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
	$da.fill($ds)
	$conn.Close()
	return $ds
}

function Graph-Iterate($arList,$varRow,$varCol,$strPass) {
	Write-Host $arList[$i].depName
	foreach($i in $arList.Keys) {
		if($arList[$i].duration -ne 0) {
			if($arList[$i].depName.Length -gt 1) {
				$varRow--
				if($arList[$i].depName -eq $null){ $arList[$i].depName = "UNKNOWN" }
				$sheet.Cells.Item($varRow,$varCol) = $arList[$i].depName
				$varRow++
				$sheet.Cells.Item($varRow,$varCol) = ("{0:N1}" -f $arList[$i].duration)
				$varCol++
				
				if($master -ne $true){ Iterate $arList[$i] $strPass }
			}
		}
	}
	return $varcol
}

function Iterate($arSub, $strCom) {
	$indSheet = $workbook.Worksheets.Add()
	$sheetName = ("{0}-{1}" -f $strCom,$arSub.depName)
	Write-Host $sheetName
	$nVar = 1
	if($sheetName -eq "CSI-OPP MAX")
	{
		Write-Host "The Var is:"
		Write-Host $nVar
		$sheetName = "{0} {1}" -f $sheetName,$nVar
		$nVar++
	}
	$strip = [System.Text.RegularExpressions.Regex]::Replace($sheetName,"[^1-9a-zA-Z_-]"," ");
	if($strip.Length -gt 31) { $ln = 31 }else{ $ln = $strip.Length }
	$indSheet.Name = $strip.Substring(0, $ln)
	$count = $arSub.Keys.Count
	$array = New-Object 'object[,]' $count,2
	$arRow = 0
	foreach($y in $arSub.Keys) {
		if($y -ne "depName" -and $y -ne "duration" -and $y.Length -gt 1) {
			$t = 0
			$array[$arRow,$t] = $y
			$t++
			$array[$arRow,$t] = $arSub[$y]
			$arRow++
		}
	}
	$rng = $indSheet.Range("A1",("B"+$count))
	$rng.Value2 = $array
}

function Create-Graph($lSheet,$lTop,$lLeft,$range, $number, $master, $catRange) {
	# Add graph to Dashboard and configure.
	$chart = $lSheet.Shapes.AddChart().Chart
	$chartNum = ("Chart {0}" -f $cvar3)
	$sheet.Shapes.Item($chartNum).Placement = 3
	$sheet.Shapes.Item($chartNum).Top = $top
	$sheet.Shapes.Item($chartNum).Left = $left
	if($master -eq $true) {
			$sheet.Shapes.Item($chartNum).Height = 500
			$sheet.Shapes.Item($chartNum).Width = 1220
		}else{
			$sheet.Shapes.Item($chartNum).Height = 325
			$sheet.Shapes.Item($chartNum).Width = 400
		}
		$chart.ChartType = 69
		$chart.SetSourceData($range)
		$chart.SeriesCollection(1).XValues = $catRange
	}

$port = "<port>"
$server = "<sqlserver>"
$db = "<db>"
$user = "<db_user>"
$password = "<pass>"
$query = "SELECT p.prid, p.account_name, p.domain_name, p.dtfirst, cs.instid, cs.sessid, cs.login_elapsed, cs.dtlast, cs.session_type, s.logon_time, s.logoff_time
FROM         dbo.principal AS p INNER JOIN
                      dbo.session AS s ON s.prid = p.prid INNER JOIN
                      dbo.ctrx_session AS cs ON cs.sessid = s.sessid"
#WHERE		p.account_name LIKE 'a[_]%'

$userlist = SQL-Connect $server $port $db $user $password $query
$users = @{}
foreach($i in $userlist.Tables) {
	if($i.account_name -notlike "h_*" -and $i.account_name -notlike "a_*" -and $i.account_name -ne "UNKNOWN" -and ([string]$i.logon_time).Length -gt 1 -and ([string]$i.logoff_time).Length -gt 1) {
		try {
			$info = Get-ADUser -Identity $i.account_name -Properties DepartmentNumber, Department, Company
		}
		catch {
			$info = @{"Company"="Terminated";"Department"="Invalid";"DepartmentNumber"="0000"}
		}
		if($info.Company.Length -lt 2) {
			$info = @{"Company"="Terminated";"Department"="Invalid";"DepartmentNumber"="0000"}
		}
		if($users.Contains($info.Company) -eq $false) {
			$users[$info.Company] = @{}
			$users[$info.Company]['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}else{
			$users[$info.Company]['duration'] = $users[$info.Company]['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}
		if($users[$info.Company].Contains(([string]$info.DepartmentNumber)) -eq $false) {
			$users[$info.Company][([string]$info.DepartmentNumber)] = @{}
			$users[$info.Company][([string]$info.DepartmentNumber)]['depName'] = $info.Department
			$users[$info.Company][([string]$info.DepartmentNumber)]['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}else{
			$users[$info.Company][([string]$info.DepartmentNumber)]['duration'] = $users[$info.Company][([string]$info.DepartmentNumber)]['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}
		if($users[$info.Company][([string]$info.DepartmentNumber)].Contains($i.account_name) -eq $false) {
			$users[$info.Company][([string]$info.DepartmentNumber)][$i.account_name] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}else{
			$users[$info.Company][([string]$info.DepartmentNumber)][$i.account_name] = $users[$info.Company][([string]$info.DepartmentNumber)][$i.account_name]+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}
	}elseif($i.account_name -ne "UNKNOWN" -and ([string]$i.logon_time).Length -gt 1 -and ([string]$i.logoff_time).Length -gt 1) {
		if($i.account_name -like "a_*") {
			$info = @{"Company"="Administrators";"Department"="Elevated IDs (A)";"DepartmentNumber"="1111"}
		}else{
			$info = @{"Company"="Administrators";"Department"="Elevated IDs (H)";"DepartmentNumber"="2222"}
		}
		if($users.Contains("Administrators") -eq $false) {
			$users['Administrators'] = @{}
			$users['Administrators']['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}else{
			$users['Administrators']['duration'] = $users['Administrators']['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}
		if($users['Administrators'].Contains($info.DepartmentNumber) -eq $false) {
			$users['Administrators'][$info.DepartmentNumber] = @{}
			$users['Administrators'][$info.DepartmentNumber]['depName'] = $info.Department
			$users['Administrators'][$info.DepartmentNumber]['duration'] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}else{
			$users['Administrators'][$info.DepartmentNumber]['duration'] = $users['Administrators'][$info.DepartmentNumber]['duration']+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}
		if($users['Administrators'][$info.DepartmentNumber].Contains($i.account_name) -eq $false) {
			$users['Administrators'][$info.DepartmentNumber][$i.account_name] = (New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}else{
			$users['Administrators'][$info.DepartmentNumber][$i.account_name] = $users['Administrators'][$info.DepartmentNumber][$i.account_name]+(New-TimeSpan $i.logon_time $i.logoff_time).TotalHours
		}
	}else{
		if(([string]$i.logon_time).Length -lt 1 -and $i.account_name -ne "UNKNOWN"){ "No logon time: "+$i.account_name }
		if(([string]$i.logoff_time).Length -lt 1 -and $i.account_name -ne "UNKNOWN"){ "No logoff time: "+$i.account_name }
	}
}

# Create Excel object, setup spreadsheet, name main page.
$excel = New-Object -ComObject excel.application
$excel.Visible = $true
$excel.DisplayAlerts = $false
$workbook = $excel.Workbooks.Add()
$row = 1
$col = 1
$sheet = $workbook.Worksheets.Item(1)
$sheet.Name = "Dashboard"

# Populate tracking vars.
# $row is the starting row to begin entering data into text cells.
# $cvar tracks $left position, resets when it reaches 3.
# $cvar3 tracks $top position, after every third graph it increments +340.
$row = 202
$col = 2
$cvar = 1
$cvar3 = 1
$top = 10
$left = 10
# Iterate through main element (Companies), $z returns company name (MGTS, MR, etc.).

$min = ($sheet.Cells.Item(($row)-1,1).Address()).Replace("$", "")
$tmin = ($sheet.Cells.Item(($row)-1,2).Address()).Replace("$", "")
foreach($q in $users.Keys) {
	$sheet.Cells.Item($row,1) = "Maritz Total Citrix Usage (by hours)"
	$row--
	if($q -eq "114"){ $q = "Training IDs" }
	$sheet.Cells.Item($row,$col) = $q
	$row++
	$sheet.Cells.Item($row,$col) = ("{0:N1}" -f $users[$q].duration)
	$col++
}
$max = ($sheet.Cells.Item($row,($col)-1).Address()).Replace("$", "")
$range = $sheet.Range($min,$max)
$range2 = $sheet.Range($tmin,$max)
Create-Graph $sheet $top $left $range $cvar3 $true $range2
$row++;$row++
$col = 2
$top = ($top)+510
$cvar3++

foreach($z in $users.Keys) {
	if($z.Length -gt 1 -and $z -ne "112 MAS"){
		# Setup chart location vars.
		if($cvar -eq 1) {
			$left = 10
		}elseif($cvar -eq 2){
			$left = 420
		}elseif($cvar -eq 3) {
			$left = 830
		}
		$col = 2
		$sheet.Cells.Item($row,1) = $z
		# Track chart range minimum cell address.
		$min = ($sheet.Cells.Item(($row)-1,1).Address()).Replace("$", "")
		$tmin = ($sheet.Cells.Item(($row)-1,2).Address()).Replace("$", "")
		# Iterate through secondary element (Departments), $i returns department name.

		# Graph-Iterate Here
		$vLoc = Graph-Iterate $users[$z] $row $col $z
		
		# Track chart range maximum cell address.
		$max = ($sheet.Cells.Item($row,($vLoc)-1).Address()).Replace("$", "")
		$range = $sheet.Range($min,$max)
		$range2 = $sheet.Range($tmin,$max)
		
		Create-Graph $sheet $top $left $range $cvar3 $false $range2
		$row++;$row++
		# Increment or reset tracking vars.
		if($cvar -eq 3) {
			$top = ($top)+340
		}
		if($cvar -eq 1 -or $cvar -eq 2){ $cvar++ }elseif($cvar -eq 3){ $cvar = 1}
		$cvar3++
	}
}
# Show dashboard page rather than some random department.
$sheet.Activate()

New-TimeSpan -Start $start -End (Get-Date)

PowerShell: I'm Going To Replace You With A Script.

We've all said it, some of us have gone to great lengths to actually DO it.

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$screen = [System.Windows.Forms.SystemInformation]::VirtualScreen
[int]$target = 60
[int]$i = 1
while($i -lt $target){
    $randx = Get-Random -Minimum 1 -Maximum $screen.Width
    $randy = Get-Random -Minimum 1 -Maximum $screen.Height
    [Windows.Forms.Cursor]::Position = "$($randx), $($randy)"
	$i++
	Start-Sleep -Seconds 10
}

Initial testing indicates this would replace approx. 80% of corporate IT. Welcome your new overlords.

Well, I supposed you'd need to up the duration...

 

PowerShell: XenServer Get VM IP Address

This like all of these PowerShell XenServer examples requires you load the snapin and an active connection to a XenServer.

First you need to get a reference to your VM of choice, in this case I know the exact VM by name so I do this:

$vms = Get-XenServer:VM | Where-Object {$_.name_label -eq "<nameofVM>"}

Now I want to read the guest metrics on this VM to find the IP Address, note that this requires XSTools to be installed and the IP address wont be available right away, typically it seems to only be available after the machine has both booted, and had time for the XSTools to report it's IP to guest metrics, but once it's available you can get it by doing this:

Get-XenServer:VM_guest_metrics.Networks -VMGuestMetrics $vms.guest_metrics

Be aware that if you want to get a list of IP's for more than one VM you will need to foreach through $vms and run the command for each one.

One potential use of this is to, say, clone a new VM from a template, start it, wait for it to establish a network connection then Test-Connection until you get a result, at which time you can proceed to do whatever else you need to do (via WinRM for instance, if you have it enabled in the template).

PowerShell: XenServer Messages (Since)

If you are expecting a message to occur after an action you are taking the following may be of use to you.

$messages = Get-XenServer:Message.Since -Since [System.DateTime]::Now
foreach($i in $messages.Values) { $i }

Obviously this doesn't do anything intelligent like look for the specific event, but once you see the output you can customize it to do whatever you want.

PowerShell: XenServer Recreate ISO Store

A little too much code to past directly on SquareSpace (haven't had time to figure out a way around that) but here it is.

PowerShell: Destroy CIFS ISO Library

This is step one of two in destroying (and then re-creating) a CIFS ISO library in XenServer, the reasons you may need to do this are varied, this is personally useful to me in a lab environment when the repo location or user credentials may change often. Everythign below requires the XenServer PSSnapin.

First thing we want to do is snag the info for the SR we want to delete:

$sr = Get-XenServer:SR | Where-Object {$_.content_type -eq "iso" -and $_.type -ne "udev" -and $_.name_label -ne "XenServer Tools"}

This filters out the CD-ROM (udev) and XS Tools and just returns us SR's whose content type is ISO, so it shouldn't matter what you name your repo (um, as long as you don't name it XenServer Tools I guess).

Now we need to unplug it:

foreach($i in $sr.PBDs){Invoke-XenServer:PBD.Unplug -SR $i}

Do you need to foreach this? Probably not. But if you DO happen to set it up with multiple PBD's then it will still fail on the next step because you only unplugged some of the PBD's, when in doubt, be thorough.

Now lets remove the SR:

Invoke-XenServer:SR.Forget -SR $sr.name_label

There you go, the next step will be to ask for a path, user and password to create a new ISO Library, which I'll cover next time.

 

App-V 5.0: Package Conversion Script

A quick PowerShell script with logging to convert a directory full of App-V packages.

$src = "<source path>\"
$dst = "<destination path>"
$logdir = "<logfile location>\ConversionLog.txt"
Import-Module AppvPkgConverter
$list = dir $src|where {$_.mode -match "d"}
If((Test-Path $logdir) -eq $false)
{
	New-Item($logdir) -Type File
}
foreach($i in $list)
{
	Write-Host $src$i
	$conv = ConvertFrom-AppvLegacyPackage -SourcePath $src$i -DestinationPath $dst
	If($conv.Error -ne $null -or $conv.Warnings -ne $null)
	{
		Add-Content -Path $logdir -Value ($conv.Source+" appears to have failed...`n")
		Add-Content -Path $logdir -Value ("Error: "+$conv.Errors+"`nWarning: "+$conv.Warnings+"`n")
	}elseif($conv.Information -ne $null){
		Add-Content $logdir $conv.Information"`n"
	}else{
		Add-Content -Path $logdir -Value ($conv.Source + " completed ok, no Errors or Warnings...`n")
	}
}

PowerShell: Persistent Environment Variables.

This comes up pretty often and if you haven't dealt with env. variables much in scripting (or even batch files) you may be confused as to why your variables don't stick around. For example:

$Env:SFT_SOFTGRIDSERVER = "appserver"

Perfectly valid, but only for that PowerShell session. Not sure why but they make you use .Net to set it permanently, and, while syntactically more complex, it isn't difficult.

[Environment]::SetEnvironmentVariable("SFT_SOFTGRIDSERVER", "appvserver", "Machine")

Likewise you can get EV's:

[Environment]::GetEnvironmentVariable("SFT_SOFTGRIDSERVER", "Machine")

It is important to note that once you set it with .Net you wont see it in $Env until you start a new session, you HAVE to retrieve it via .Net in order to see it in the same session. If you really need some functionality of $Env you can add it both ways, but I honestly can't think of a good reason you would need to do that. But it wont harm anything.

PowerShell: Get command definition.

With 5.0 being so PowerShell friendly, if you spend a decent amount of time in the shell you probably noticed that the definition of commands tends to get truncated when you, say Get-Command Test-LegacyAppVPackage. Which returns something like this (apologies for the horrible formatting to follow):


CommandType	Name					Definition
-----------	----					----------
Cmdlet		Test-LegacyAppvPackage	Test-LegacyAppvPackage [-Source] <String[]> [-Ve...

CommandType	Name					Definition
-----------	----					----------
Cmdlet		Test-LegacyAppvPackage	Test-LegacyAppvPackage [-Source] <String[]> [-Ve...

You might find this trick handy (and for more than just this if you are clever).

$(Get-Command Test-LegacyAppVPackage).Definition

Which returns something like this:

Test-LegacyAppvPackage [-Source] <String[]> [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <Acti

onPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]

Much better...

Killing A Process Safely...

Anyone who has ever done any real scripting in Windows knows there are about a million different ways to kill a process.

However, be it VBScript or PowerShell there is only really one "safe" way (not including heavily complicated .Net calls in PS):

taskkill /t /pid <pid>

A couple of quick details. /t tell it to "treekill", or in other words kill all child processes as well (which is pretty crucial when killing virtual apps as their child processes hold the virtual environment open).

/pid is pretty self explanatory, you can find a processes PID pretty easily:

 

$proc = Get-Process -ProcessName WINWORD
$proc.ID
$proc.ID should contain the PID for the WINWORD process, in my case it was 344, so the resultant safe-kill command (which WILL prompt the user to save any unsaved documents) would be:
taskkill /t /pid 344
Programattically you can then have the script wait, check to see if the process still exists (after, say, 30-60 seconds for the user to save their files if they so choose), and then proceed from their, either moving on to whever task was at hand if it is stopped, or force killing the process if it is still running.

PowerShell: Run via SCCM with Administrative rights.

If you have tried to run a PowerShell script before with SCCM you might have found it odd and not exactly intuitive. Here are a couple tips.

The most frustrating part of this problem is simply...not being able to tell what is wrong. The error message comes and goes before you have a chance of seeing it.

It isn't neccessary to do this (as I've already done it), but to solve the problem I modified my SCCM command line as follows:

%COMSPEC% /K powershell.exe -noprofile -file script.ps1 -executionpolicy Bypass

When running this the first thing you will notice is an error from cmd.exe saying UNC paths are not supported, reverting back to the windows directory. Well now your script wont launch because there is no correct working directory (which WOULD have been the network share on your distribution point). To get around THIS you have to set the following:

Key: HKLM\Software\Wow6432Node\Microsoft\Command Processor

Value: DisableUNCCheck

Data (DWORD): 1

Now if you run your program again you will see it says the execution of scripts on the local machine has been disabled. Luckily you have a hand dandy command prompt (thanks to the /K switch) so you can type powershell -command "Get-ExecutionPolicy -list".

You will see that everything is Undefined. If you go open up a regular command prompt and type the same thing, you should see whatever your actual settings are (in my case it was Bypass set to the LocalMachine scope and everything else undefined, this was set to Bypass for TESTING reasons).

A whoami in the SCCM kicked command prompt shows nt authority\system as you would expect.

So the problem appears to be that when run as the system account -executionpolicy is ignored and it doesn't appear to be getting/setting it's execution policy in the same place everything else is.

For instance, right now on the same machine I have two windows open, one powershell run as administrator (via a domain account in the local admins group), the other via the command prompt SCCM launches. Here are the Get-ExecutionPolicy -list results from each:

Local Admin:


SCCM

Same machine, two different settings. First attempt was to use:

powershell.exe -noprofile -command "Set-ExecutionPolicy Bypass LocalMachine" -File script.ps1

This failed and ultimately it appears that powershell will either run -command or -file, but not both.

So the solution to running PowerShell scripts as admin via SCCM is to do the following:

Create an SCCM Program with the following command line:

powershell.exe -noprofile -command "Set-ExecutionPolicy Bypass LocalMachine"

Then one with the following:

powershell.exe -noprofile -file script.ps1

And finally a cleanup program:

powershell.exe -noprofile -command "Set-ExecutionPolicy RemoteSigned LocalMachine"

Obviously RemoteSigned should be whatever your organization has decided as the standard Execution Policy level, the default I believe is Restricted, most will probably use RemoteSigned, security (over)conscious will probably use AllSigned.

Is this ideal? No. Of course not. It paying attention to the -ExecutionPolicy switch would be ideal.

But it works.

And on a bit of a side tangent, I think it is a pretty "microsoft" view of security to put this rather convoluted security system in place, and then still have VBScripts executable right out the gate on Windows 7 box. And batch files.

What is the point of all this obnoxious security when, at the end of the day...they can just use VBScript. Had they made you toggle a setting somewhere to enable VBScript and Batch files, had they not made 5 scopes of security policy for powershell, had they basically not admitted that they don't know how to do a secure scripting language (your security should probably come from the user rights, and not purely the execution engine, though, that's a finer point you could argue several ways), I'd probably be a lot more sympathetic.

Had SCCM not been such a myopic monolithic dinosaur, this wouldn't be a problem. These are all symptoms of what will eventually kill them. Legacy. 64-bit filesystem redirection, the registry as a whole, more specifically Wow6432Node, Program Files (x86), needing to use PowerShell, VBScript or Batch files to do a simple file transfer/shortcut placement.

This is System Center Configuration Manager. It can use bits to copy a multi-gigabyte install "safely" to a client machine...but only if it's then going to run an installer.

It can't copy a file and place a shortcut, or add a key to the registry, it only manages the configuration in the most outmoded and obsolete ways possible.

/rant

RSAT: Enable via Powershell

You can use wusa <path to .msu> /quiet to install the update, then run this powershell script to enable all the features for RSAT.

 

&$env:systemroot\SysNative\dism.exe /Online /Enable-Feature:IIS-WebServerRole
&$env:systemroot\SysNative\dism.exe /Online /Enable-Feature:IIS-WebServerManagementTools
&$env:systemroot\SysNative\dism.exe /Online /Enable-Feature:IIS-IIS6ManagementCompatibility
&$env:systemroot\SysNative\dism.exe /Online /Enable-Feature:IIS-Metabase
&$env:systemroot\SysNative\dism.exe /Online /Enable-Feature:IIS-LegacySnapIn
&$env:systemroot\SysNative\dism.exe /online /get-features | Select-String -Pattern remote* | %{$Exec = "dism.exe /online /enable-feature /featurename:RemoteServerAdministrationTools /featurename:" + ($_).ToString().Replace('Feature Name : ',''); Invoke-expression $Exec}
&$env:systemroot\SysNative\dism.exe /Online /Enable-Feature:RemoteServerAdministrationTools-Roles-AD-DS-AdministrativeCenter

 

via Xenophane

Update: If you don't want the SMTP Server Tools installed you can ditch the dism lines that turn on all the IIS components and just run the /get-features line and the AdminCenter line after it. The reason for running Admin Center again is because it shows up in the list before its pre-req so it fails to enable the first time around, so you just enable it again and viola...

Update 2: I modified the script and had the WiseScript call it because SCCM kinda blows. I had to modify the script because calling it with WiseScript tripped the issue with DISM reverting to the x86 version, which errors out, so now it's pointing to the SysNative version (another brilliant idea from microsoft, I guess calling it dism64 or defaulting to x64 and not x86 or about fifty other options made too much sense and therefor had to go).

PowerShell: App-V Suite-In-Place

A powershell script to suite two applications in the local OSD cache, for testing interaction and hotfixing a user if need be. At some point I may update it to accept arguments.

$offcGuid = "22C76228-3DF0-48DD-853C-77FDC955CC86"
$sPath = "RTSPS://%SFT_SOFTGRIDSERVER%:322/Power Pivot for Excel.sft"
$sGuid = "F5B20FA7-E437-4E03-885B-3D5B67F3DC22"
$sParam = ""
$sFile = "%CSIDL_PROGRAM_FILES%\Microsoft Analysis Services\AS Excel Client\10\Microsoft.AnalysisServices.AtomLauncher.exe"
$sGuard = "POWPIVOT.1\osguard.cp"
$sSize = "323267785"
$sMand = "FALSE"

$path = "C:\ProgramData\Microsoft\Application Virtualization Client\SoftGrid Client\OSD Cache\"
$dir = Get-ChildItem $path
$list = $dir | where {$_.extension -eq ".osd"}

foreach ($i in $list)
{
    [xml] $xml = gc ($path + $i)
    if($xml.SOFTPKG.IMPLEMENTATION.CODEBASE.GUID -eq $offcGuid)
    {
        $final = ($path + $i)
        
        [xml] $excel = gc $final

        $newitem = $excel.CreateElement("CODEBASE")
        $newitem.SetAttribute("HREF", $sPath)
        $newitem.SetAttribute("GUID", $sGuid)
        $newitem.SetAttribute("PARAMETERS", $sParam)
        $newitem.SetAttribute("FILENAME", $sFile)
        $newitem.SetAttribute("SYSGUARDFILE", $sGuard)
        $newitem.SetAttribute("SIZE", $sSize)
        $newitem.SetAttribute("MANDATORY", $sMand)
        
        $excel.SOFTPKG.IMPLEMENTATION.VIRTUALENV.DEPENDENCIES.AppendChild($newitem)
        $excel.Save($final)
    }
}