Tag Archives: configuration manager 2012

ConfigMgr and a backlog in distributions

Scenario

Do you have a primary site and a few secondary sites in ConfigMgr 2012+?

Do you schedule the legacy Package format to update on a schedule?

image

Do you have a backlog in the distribution manager?

Well, so far this is known (by Microsoft) defect that apparently is yet to be fixed (until 1606 – nothing confirmed beyond that)

Symptoms

If you review the database where ConfigMgr resides you can see that there is a constant growing amount of DistributionJobs. Sample query to get an overview;

use <database>
select COUNT(*) from distributionjobs

The problem grows the more packages you have set to update on a schedule. The frequency of the schedule is not relevant, the package will loop into a forever updating loop. Most likely the primary site will handle this efficiently, however the sending to secondary sites will cause a backlog that is not just an annoyance but causing severe problems as the backlog will continue to grow.

Repeating this: The frequency of the schedule is not relevant. Just check the above checkbox and the issue will occur.

SQL query to locate relevant packages

use <database>
select pkg.PkgID, pkg.Manufacturer, pkg.Name, pkg.Version, pkg.Language, pkg.RefreshSchedule from SMSPackages as pkg
where datalength(pkg.RefreshSchedule) !=0

Fixit

Easy – uncheck all these check-boxes that updates packages. If you still want to update packages on a schedule use a powershell script to trigger the update and use the task scheduler to run the update.

Run the command-line;

powershell -executionpolicy bypass -file SCCM.UpdatePkg.ps1 -packageid <PACKAGEID>

Code:
(I honestly don’t know if I have stolen / copied this from somewhere – if I have give me a ping and I will remove this)

#========================================================================
# Created on: 2014-10-28 15:06
# Created by: Nicke Källén
# Organization: Applepie.se
# Filename: SCCM.UpdatePkg.ps1
#========================================================================
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$packageid
)

Function Invoke-CMPackageUpdate
{
[CmdLetBinding()]
Param(
[Parameter(Mandatory=$True,HelpMessage="Please Enter Primary Server Site code")]
$SiteCode,
[Parameter(Mandatory=$True,HelpMessage="Please Enter Primary Server Name")]
$SiteServer,
[Parameter(Mandatory=$True,HelpMessage="Please Enter Package/Application ID")]
$PackageID
)

Try{
$PackageClass = [wmiclass] "\\$($siteserver)\root\sms\site_$($sitecode):SMS_Package"
$newPackage = $PackageClass.CreateInstance()

$newPackage.PackageID = $PackageID

$newPackage.RefreshPkgSource()
}
Catch{
$_.Exception.Message
}

}

Invoke-CMPackageUpdate -SiteCode <SITECODE> -SiteServer <SERVER> -PackageID $packageid

Software Center can not be loaded

Regardless of what version of the ConfigMgr agent (2012 –> 1602) you get – there still seems to be a possibility to have left-overs from ConfigMgr 2007.

within the SCClient log-file the following error would be generated;

Exception Microsoft.SoftwareCenter.Client.Data.WmiException: Provider load failure       (Microsoft.SoftwareCenter.Client.SingleInstanceApplication at OnGetException)

The following is presented to the user when starting Software Center

image

Software Center can not be loaded. There is a problem loading the required components for Software Center.

It seems that this is due to a reference no longer in use – the dcmsdk.dll, located under SysWOW64 (on 32-bit systems). Sample output using reg query:

HKEY_LOCAL_MACHINE\Software\Wow6432node\classes\CLSID\{555B0C3E-41BB-4B8A-A8AE-8A9BEE761BDF}
(Default)    REG_SZ    Configmgr Desired Configuration WMI Provider

HKEY_LOCAL_MACHINE\Software\Wow6432node\classes\CLSID\{555B0C3E-41BB-4B8A-A8AE-8A9BEE761BDF}\InProcServer32
(Default)    REG_SZ    C:\WINDOWS\SysWOW64\CCM\dcmsdk.dll

End of search: 2 match(es) found.

Fix? Delete the registry key – sample command line;

reg delete HKLM\Software\Wow6432node\classes\CLSID\{555B0C3E-41BB-4B8A-A8AE-8A9BEE761BDF} /f

ConfigMgr: Match client address to IP-Range Boundaries

Despite the Microsoft recommendation, primarily due to additional workload that it causes, to not leverage IP-Ranges we have noticed a far greater significant accuracy of where clients retrieve content from based on our IP-ranges. So yes, we have our boundaries, with few exceptions, setup using IP-ranges.

We also have clients spread around the globe, new networks beeing spun-up, networks that aren’t supposed to be used for servers and clients and much more to actually be used for these type of things. The issue at hand is to understand where clients are actually connecting from, and what locations we know about.

To get some type of insight of where ConfigMgr clients are actually connecting from we started polling our database. In the end – this turned into two SQL-queries that would get all the IP-range boundaries, and a summary of how many clients we support on each network. As lazy as one can be – this ended up gathering enough information to present to other teams to present where clients are connecting from, how many there are and that we don’t previously didn’t know about this location.

To list how many clients you have per a /24-subnet. This may of course not necessarily be the exact size of a subnet, but it allows for an easy count-up of clients.

select SUBSTRING(ip.IPAddress0, 1, 
LEN(ip.IPAddress0) - CHARINDEX('.',REVERSE(ip.IPAddress0))) + ".1" As IP,
 COUNT(*) as Devices
 from v_Network_DATA_Serialized as ip 
where ip.IPAddress0 IS NOT NULL and ip.IPSubnet0 != "64"
and ip.DNSDomain0 like "%yourdomain.com"
and ip.TimeStamp > DATEADD(day, -10, GETDATE())
GROUP BY  SUBSTRING(ip.IPAddress0, 1, LEN(ip.IPAddress0) - CHARINDEX('.',REVERSE(ip.IPAddress0)))
ORDER BY Devices DESC

A list of all boundaries where we split the start and end IP-address of a specific range

select bound.DisplayName,
SUBSTRING(bound.value,1,CHARINDEX('-',bound.value) -1) AS LEFTHALF,
SUBSTRING(bound.value,CHARINDEX('-',bound.value) +1 ,100) AS RIGHTHALF
from vSMS_Boundary as bound
where bound.BoundaryType = "3"
and bound.DisplayName != "some boundary to exclude"

Information about the clients within a specific range that we do not know about

select DNSHostName0,
DNSDomain0,
IPAddress0,
IPSubnet0,
DefaultIPGateway0,
DHCPServer0
from v_Network_DATA_Serialized as ip
where ip.IPAddress0 IS NOT NULL
and ip.IPSubnet0 != '64'
and ip.DNSDomain0 like '%yourdomain.com'
and ip.TimeStamp > DATEADD(day, -10, GETDATE())
and ip.IPaddress0 like 'XXX.YYY.ZZZ.%'

 

To join all of this information together some basic, crude, logic was built in powershell to match up networks that clients are in and that we know about. The function to perform the actual IP-range lookup is from stackoverflow-reply. Sample output first:2015-11-22 16_07_41-Clipboard

 

 

 

function IsIpAddressInRange {
param(
 [string] $ipAddress,
 [string] $fromAddress,
 [string] $toAddress
 )

 $ip = [system.net.ipaddress]::Parse($ipAddress).GetAddressBytes()
 [array]::Reverse($ip)
 $ip = [system.BitConverter]::ToUInt32($ip, 0)

 $from = [system.net.ipaddress]::Parse($fromAddress).GetAddressBytes()
 [array]::Reverse($from)
 $from = [system.BitConverter]::ToUInt32($from, 0)

 $to = [system.net.ipaddress]::Parse($toAddress).GetAddressBytes()
 [array]::Reverse($to)
 $to = [system.BitConverter]::ToUInt32($to, 0)

 $from -le $ip -and $ip -le $to
}


$ErrorActionPreference = "silentlycontinue"
$database = "ConfigMgrServer"
$datasource = "ConfigMgrDB"

$netquery = "select SUBSTRING(ip.IPAddress0, 1, LEN(ip.IPAddress0) - CHARINDEX('.',REVERSE(ip.IPAddress0))) + '.1' As IP, COUNT(*) as Devices from v_Network_DATA_Serialized as ip where ip.IPAddress0 IS NOT NULL and ip.IPSubnet0 != '64' and ip.DNSDomain0 like '%yourdomain.com' and ip.TimeStamp > DATEADD(day, -10, GETDATE()) GROUP BY SUBSTRING(ip.IPAddress0, 1, LEN(ip.IPAddress0) - CHARINDEX('.',REVERSE(ip.IPAddress0))) ORDER BY Devices DESC"

$networks= Invoke-Sqlcmd -Query $netquery -server $datasource -Database $database

$query = "select bound.DisplayName, SUBSTRING(bound.value,1,CHARINDEX('-',bound.value) -1) AS LEFTHALF,SUBSTRING(bound.value,CHARINDEX('-',bound.value) +1 ,100) AS RIGHTHALF from vSMS_Boundary as bound where bound.BoundaryType = '3' and bound.DisplayName != 'exclusion boundary'"

$iprange = Invoke-Sqlcmd -Query $query -server $datasource -Database $database

foreach ($net in $networks) {
 if (!($net.ip -eq '192.168.1.1')) {
 $i = 0
 $J = $iprange.count
 $boundaryfound = $false
 do {
 if (IsIpAddressInRange $net.ip $iprange[$i].LEFTHALF $iprange[$i].RIGHTHALF)
 {

 $boundaryfound = $true
 }
 $i++
 } until ($i -gt $j)
 if ($boundaryfound -eq $false) 
 {
 write-host "Network: $($net.ip) - Devices: $($net.Devices)"

 #$($($net.ip) -replace ".$")
 $devquery = "select DNSHostName0,DNSDomain0,IPAddress0,IPSubnet0,DefaultIPGateway0,DHCPServer0 from v_Network_DATA_Serialized as ip
 where ip.IPAddress0 IS NOT NULL
 and ip.IPSubnet0 != '64'
 and ip.DNSDomain0 like '%yourdomain.com'
 and ip.TimeStamp > DATEADD(day, -10, GETDATE())
 and ip.IPaddress0 like '$($($net.ip) -replace ".$")%'"
 $devices= Invoke-Sqlcmd -Query $devquery -server $datasource -Database $database
 $devices
 

 }
 }
}

ConfigMgr client install – Error 0x80070008. File may be corrupt

While troubleshooting the failure of the ConfigMgr 2012 R2 Client to install on a generic Windows 7 x64 installation on a machine we spent numerous hours digesting the following error code that refused the completion of the installation in the CCMSETUP.log file

Failed extract manifest file from cab file 'C:\WINDOWS\ccmsetup\ccmsetup.cab'. Error 0x80070008.  File may be corrupt.

In the end it seemed that the inkling that the ccmsetup.cab was corrupted was false (surprise!), however the client did have issues extracting the manifest from it and so the installation halted.

Looking up the error code a generic answer is given:

Not enough storage is available to process this command.

Source: Windows

The error code immediately leads to a KB-article by Microsoft (and written in similiar style by many other vendors and bloggers) that would hint towards manipulating the IRPStackSize registry key for older (Windows 2000..) versions of Windows to resolve the issue. Unfortunately Windows 7 will not be so easily tamed.

After reviewing the Process Monitor logs one idea came that it utilized the CABINET.DLL in C:\Windows\System32 to extract the information. Review the details tab revealed this;

 

image

A client without the issues gave us these properties:

clip_image002

 

Most likely a execution of SFC /SCANNOW might have resolved the issue, however since we knew the file we were interested in it was simply replaced with a known working version from a different machine.

Symantec Endpoint Protection 12.X on OSX

If you are using ConfigMgr 2012 (or one of the plugins hi-jacking the infrastructure – such as Parallels) to manage the Mac OSX devices there are some caveats to the ordinary guide of both from Symantec on howto install the Symantec Endpoint Protection aswell as the Microsoft guide “How to Create and Deploy Applications for Mac Computers in Configuration Manager”.

First of all you need access to the Symantec Endpoint Protection media and to actually start the installation. Once its started you can immediately headover to Symantecs guide on howto Deploy (keyword deploy) SEP with Apple Remote Desktop or Casper.

The guide states that once the installation is fired up (and you acknowledge that its OK if this requires you to restart the computer) you can access the Tools-menu

image

Clicking the “Create remote deployment package” will immediately fire off a new menu that will allow you to choose a file-name and a place where the new package can be saved.

image

Once the deployment package is created you you will receive a helpful note about only deploying this with a deployment system, and remembering to restart afterwards.

image

As per the ConfigMgr article on howto deploy applications for Mac there is a need to convert the generic PKG-format to the ConfigMgr compatible (and unique) CMMAC format.

This specific package does unfortunately not provide any detection mechanism, so the command-line to convert this package is.

./CMApputil –c SEPRemote.pkg –o /volumes/usbstick/ -s

-c points the utility to our original package

-o points to the where we want to place our final package (named SEPRemote.cmmac)

-s will omit the creation of the detection rules

AppsNotify – Ping the Application Catalog

One downside with Configuration Manager 2012 compared to Configuration Manager 2007 is that applications deployed to users, only made available, will not give a notification on the endpoint. This caused some initial confusion that needed a blog-post to clarify the matter – as all admins were used to a single view on the client what could be installed.

Microsoft created the Application Catalog, and for newer devices (mobile and Windows 8+) they made a secondary interface called the Company Portal (read about deployment at Justin Chalfants blog). However, none of these give a notification to the end user if an application is only made available.

Therefore I created a script in PowerShell that can just do that – ping the Application Catalog – check if there are new apps, and if so notify the user.

How does it work?

A scheduled task is setup

image

It starts the application 15 minutes after logon (to avoid excessive workload..), and then runs every 15 minutes.

After that, the workflow is something like;

Connect to Application Catalog

  • If no previous check is completed, gather a list.No notification is given.
  • If a previous check is completed, compare it.
    • No difference? Do nothing
    • New applications available? Notify the user
      • Maintain a notification in the system tray (which directs the user to the Application Catalog if clicked)
    • Applications removed? Update the list, no notification to the end-user

 

For each check there is a log file within the users %TEMP% called appsnotify app.log. Sample output;

image

How does it look?

Like this;

appsnotify

 

How do I deploy it?

MSI-file to install it

To install it use the MSI file with the property APPCATALOG. Input should be;

APPCATALOG=http://localhost/CMApplicationCatalog

(no slash at the end)

The Application Catalog needs to be prepared for client interaction which is described by Microsoft in a blog-article. See the Getting Started section of Extending the Application Catalog in System Center 2012 Configuration Manager.

How do I make it my own?

In all Community spirit, here comes the code / files.

XML-file for creating the scheduled task – incase you want to build your own

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-16&quot;?&gt;
&lt;Task version=&quot;1.2&quot; xmlns=&quot;http://schemas.microsoft.com/windows/2004/02/mit/task&quot;&gt;
  &lt;RegistrationInfo&gt;
    &lt;Author&gt;PRECISION\Nicke&lt;/Author&gt;
  &lt;/RegistrationInfo&gt;
  &lt;Triggers&gt;
    &lt;LogonTrigger&gt;
      &lt;Repetition&gt;
        &lt;Interval&gt;PT15M&lt;/Interval&gt;
        &lt;StopAtDurationEnd&gt;false&lt;/StopAtDurationEnd&gt;
      &lt;/Repetition&gt;
      &lt;StartBoundary&gt;1899-12-30T06:04:14&lt;/StartBoundary&gt;
      &lt;Enabled&gt;true&lt;/Enabled&gt;
      &lt;Delay&gt;PT15M&lt;/Delay&gt;
    &lt;/LogonTrigger&gt;
  &lt;/Triggers&gt;
  &lt;Principals&gt;
    &lt;Principal id=&quot;Author&quot;&gt;
      &lt;GroupId&gt;S-1-5-32-545&lt;/GroupId&gt;
      &lt;RunLevel&gt;LeastPrivilege&lt;/RunLevel&gt;
    &lt;/Principal&gt;
  &lt;/Principals&gt;
  &lt;Settings&gt;
    &lt;MultipleInstancesPolicy&gt;IgnoreNew&lt;/MultipleInstancesPolicy&gt;
    &lt;DisallowStartIfOnBatteries&gt;false&lt;/DisallowStartIfOnBatteries&gt;
    &lt;StopIfGoingOnBatteries&gt;false&lt;/StopIfGoingOnBatteries&gt;
    &lt;AllowHardTerminate&gt;false&lt;/AllowHardTerminate&gt;
    &lt;StartWhenAvailable&gt;true&lt;/StartWhenAvailable&gt;
    &lt;RunOnlyIfNetworkAvailable&gt;true&lt;/RunOnlyIfNetworkAvailable&gt;
    &lt;IdleSettings&gt;
      &lt;StopOnIdleEnd&gt;true&lt;/StopOnIdleEnd&gt;
      &lt;RestartOnIdle&gt;false&lt;/RestartOnIdle&gt;
    &lt;/IdleSettings&gt;
    &lt;AllowStartOnDemand&gt;true&lt;/AllowStartOnDemand&gt;
    &lt;Enabled&gt;true&lt;/Enabled&gt;
    &lt;Hidden&gt;true&lt;/Hidden&gt;
    &lt;RunOnlyIfIdle&gt;false&lt;/RunOnlyIfIdle&gt;
    &lt;WakeToRun&gt;false&lt;/WakeToRun&gt;
    &lt;ExecutionTimeLimit&gt;PT0S&lt;/ExecutionTimeLimit&gt;
    &lt;Priority&gt;7&lt;/Priority&gt;
  &lt;/Settings&gt;
  &lt;Actions Context=&quot;Author&quot;&gt;
    &lt;Exec&gt;
      &lt;Command&gt;C:\Program Files (x86)\Common Files\AppsNotify\AppsNotify 2.0.exe&lt;/Command&gt;
      &lt;Arguments&gt;-appcatalog http://www.sample.com&lt;/Arguments&gt;
    &lt;/Exec&gt;
  &lt;/Actions&gt;
&lt;/Task&gt;

Source code for the PowerShell script. Wrapped it into a .exe with PowerShell Studio 2012. I have only tested this on Windows 7 x64 with PowerShell 3.0 / 2.0. Log-functions are from 9to5it

#========================================================================
# Created on: 2014-06-10
# Created by: Nicke Källén
# Organization:
# Filename: AppsNotify 2.0.pff
#========================================================================

$AppNotify.FormBorderStyle = 'FixedToolWindow'
Function Log-Start{
 &lt;#
 .SYNOPSIS
 Creates log file

 .DESCRIPTION
 Creates log file with path and name that is passed. Checks if log file exists, and if it does deletes it and creates a new one.
 Once created, writes initial logging data

 .PARAMETER LogPath
 Mandatory. Path of where log is to be created. Example: C:\Windows\Temp

 .PARAMETER LogName
 Mandatory. Name of log file to be created. Example: Test_Script.log

 .PARAMETER ScriptVersion
 Mandatory. Version of the running script which will be written in the log. Example: 1.5

 .INPUTS
 Parameters above

 .OUTPUTS
 Log file created

 .NOTES
 Version: 1.0
 Author: Luca Sturlese
 Creation Date: 10/05/12
 Purpose/Change: Initial function development

 Version: 1.1
 Author: Luca Sturlese
 Creation Date: 19/05/12
 Purpose/Change: Added debug mode support

 .EXAMPLE
 Log-Start -LogPath &quot;C:\Windows\Temp&quot; -LogName &quot;Test_Script.log&quot; -ScriptVersion &quot;1.5&quot;
 #&gt;

 [CmdletBinding()]

 Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][string]$LogName, [Parameter(Mandatory=$true)][string]$ScriptVersion)

 Process{
 $sFullPath = $LogPath + &quot;\&quot; + $LogName

 #Check if file exists and delete if it does
 If((Test-Path -Path $sFullPath)){
 Remove-Item -Path $sFullPath -Force
 }

 #Create file and start logging
 New-Item -Path $LogPath -Name $LogName –ItemType File

 Add-Content -Path $sFullPath -Value &quot;***************************************************************************************************&quot;
 Add-Content -Path $sFullPath -Value &quot;Started processing at [$([DateTime]::Now)].&quot;
 Add-Content -Path $sFullPath -Value &quot;***************************************************************************************************&quot;
 Add-Content -Path $sFullPath -Value &quot;&quot;
 Add-Content -Path $sFullPath -Value &quot;Running script version [$ScriptVersion].&quot;
 Add-Content -Path $sFullPath -Value &quot;&quot;
 Add-Content -Path $sFullPath -Value &quot;***************************************************************************************************&quot;
 Add-Content -Path $sFullPath -Value &quot;&quot;

 #Write to screen for debug mode
 Write-Debug &quot;***************************************************************************************************&quot;
 Write-Debug &quot;Started processing at [$([DateTime]::Now)].&quot;
 Write-Debug &quot;***************************************************************************************************&quot;
 Write-Debug &quot;&quot;
 Write-Debug &quot;Running script version [$ScriptVersion].&quot;
 Write-Debug &quot;&quot;
 Write-Debug &quot;***************************************************************************************************&quot;
 Write-Debug &quot;&quot;

 }
}

Function Log-Write{
 &lt;#
 .SYNOPSIS
 Writes to a log file

 .DESCRIPTION
 Appends a new line to the end of the specified log file

 .PARAMETER LogPath
 Mandatory. Full path of the log file you want to write to. Example: C:\Windows\Temp\Test_Script.log

 .PARAMETER LineValue
 Mandatory. The string that you want to write to the log

 .INPUTS
 Parameters above

 .OUTPUTS
 None

 .NOTES
 Version: 1.0
 Author: Luca Sturlese
 Creation Date: 10/05/12
 Purpose/Change: Initial function development

 Version: 1.1
 Author: Luca Sturlese
 Creation Date: 19/05/12
 Purpose/Change: Added debug mode support

 .EXAMPLE
 Log-Write -LogPath &quot;C:\Windows\Temp\Test_Script.log&quot; -LineValue &quot;This is a new line which I am appending to the end of the log file.&quot;
 #&gt;

 [CmdletBinding()]

 Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][string]$LineValue)

 Process{
 Add-Content -Path $LogPath -Value $LineValue

 #Write to screen for debug mode
 Write-Debug $LineValue
 }
}

Function Log-Error{
 &lt;#
 .SYNOPSIS
 Writes an error to a log file

 .DESCRIPTION
 Writes the passed error to a new line at the end of the specified log file

 .PARAMETER LogPath
 Mandatory. Full path of the log file you want to write to. Example: C:\Windows\Temp\Test_Script.log

 .PARAMETER ErrorDesc
 Mandatory. The description of the error you want to pass (use $_.Exception)

 .PARAMETER ExitGracefully
 Mandatory. Boolean. If set to True, runs Log-Finish and then exits script

 .INPUTS
 Parameters above

 .OUTPUTS
 None

 .NOTES
 Version: 1.0
 Author: Luca Sturlese
 Creation Date: 10/05/12
 Purpose/Change: Initial function development

 Version: 1.1
 Author: Luca Sturlese
 Creation Date: 19/05/12
 Purpose/Change: Added debug mode support. Added -ExitGracefully parameter functionality

 .EXAMPLE
 Log-Error -LogPath &quot;C:\Windows\Temp\Test_Script.log&quot; -ErrorDesc $_.Exception -ExitGracefully $True
 #&gt;

 [CmdletBinding()]

 Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][string]$ErrorDesc, [Parameter(Mandatory=$true)][boolean]$ExitGracefully)

 Process{
 Add-Content -Path $LogPath -Value &quot;Error: An error has occurred [$ErrorDesc].&quot;

 #Write to screen for debug mode
 Write-Debug &quot;Error: An error has occurred [$ErrorDesc].&quot;

 #If $ExitGracefully = True then run Log-Finish and exit script
 If ($ExitGracefully -eq $True){
 Log-Finish -LogPath $LogPath
 Break
 }
 }
}

Function Log-Finish{
 &lt;#
 .SYNOPSIS
 Write closing logging data &amp; exit

 .DESCRIPTION
 Writes finishing logging data to specified log and then exits the calling script

 .PARAMETER LogPath
 Mandatory. Full path of the log file you want to write finishing data to. Example: C:\Windows\Temp\Test_Script.log

 .PARAMETER NoExit
 Optional. If this is set to True, then the function will not exit the calling script, so that further execution can occur

 .INPUTS
 Parameters above

 .OUTPUTS
 None

 .NOTES
 Version: 1.0
 Author: Luca Sturlese
 Creation Date: 10/05/12
 Purpose/Change: Initial function development

 Version: 1.1
 Author: Luca Sturlese
 Creation Date: 19/05/12
 Purpose/Change: Added debug mode support

 Version: 1.2
 Author: Luca Sturlese
 Creation Date: 01/08/12
 Purpose/Change: Added option to not exit calling script if required (via optional parameter)

 .EXAMPLE
 Log-Finish -LogPath &quot;C:\Windows\Temp\Test_Script.log&quot;

 .EXAMPLE
 Log-Finish -LogPath &quot;C:\Windows\Temp\Test_Script.log&quot; -NoExit $True
 #&gt;

 [CmdletBinding()]

 Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$false)][string]$NoExit)

 Process{
 Add-Content -Path $LogPath -Value &quot;&quot;
 Add-Content -Path $LogPath -Value &quot;***************************************************************************************************&quot;
 Add-Content -Path $LogPath -Value &quot;Finished processing at [$([DateTime]::Now)].&quot;
 Add-Content -Path $LogPath -Value &quot;***************************************************************************************************&quot;

 #Write to screen for debug mode
 Write-Debug &quot;&quot;
 Write-Debug &quot;***************************************************************************************************&quot;
 Write-Debug &quot;Finished processing at [$([DateTime]::Now)].&quot;
 Write-Debug &quot;***************************************************************************************************&quot;

 #Exit calling script if NoExit has not been specified or is set to False
 If(!($NoExit) -or ($NoExit -eq $False)){
 Exit
 }

 }
}

function Get-ScriptDirectory
{
if($hostinvocation -ne $null)
{
Split-Path $hostinvocation.MyCommand.path
}
else
{
Split-Path $script:MyInvocation.MyCommand.Path
}
} 

function Parse-Commandline
{
&lt;#
 .SYNOPSIS
 Parses the Commandline of a package executable

 .DESCRIPTION
 Parses the Commandline of a package executable

 .PARAMETER Commandline
 The Commandline of the package executable

 .EXAMPLE
 $arguments = Parse-Commandline -Commandline $Commandline

 .INPUTS
 System.String

 .OUTPUTS
 System.Collections.Specialized.StringCollection
#&gt;

 [OutputType([System.Collections.Specialized.StringCollection])]
 Param([string]$CommandLine) 

 $Arguments = New-Object System.Collections.Specialized.StringCollection 

 if($CommandLine)
 {
 #Find First Quote
 $index = $CommandLine.IndexOf('&quot;') 

 while ( $index -ne -1)
 {#Continue as along as we find a quote
 #Find Closing Quote
 $closeIndex = $CommandLine.IndexOf('&quot;',$index + 1)
 if($closeIndex -eq -1)
 {
 break #Can’t find a match
 }
 $value = $CommandLine.Substring($index + 1,$closeIndex – ($index + 1))
 [void]$Arguments.Add($value)
 $index = $closeIndex 

 #Find First Quote
 $index = $CommandLine.IndexOf('&quot;',$index + 1)
 }
 }
 return $Arguments
}

function Convert-CommandLineToDictionary
{
 &lt;#
 .SYNOPSIS
 Parses and converts the commandline of a packaged executable into a Dictionary

 .DESCRIPTION
 Parses and converts the commandline of a packaged executable into a Dictionary

 .PARAMETER Dictionary
 The Dictionary to load the value pairs into.

 .PARAMETER CommandLine
 The commandline of the package executable

 .PARAMETER ParamIndicator
 The character used to indicate what is a parameter.

 .EXAMPLE
 $Dictionary = New-Object System.Collections.Specialized.StringDictionary
 Convert-CommandLineToDictionary -Dictionary $Dictionary -CommandLine $Commandline -ParamIndicator '-'
 #&gt;
 Param( [ValidateNotNull()]
 [System.Collections.Specialized.StringDictionary]$Dictionary,
 [string]$CommandLine,
 [char] $ParamIndicator) 

 $Params = Parse-Commandline $CommandLine

 for($index = 0; $index -lt $Params.Count; $index++)
 {
 [string]$param = $Params[$index]
 #Clear the values
 $key = &quot;&quot;
 $value = &quot;&quot; 

 if($param.StartsWith($ParamIndicator))
 {
 #Remove the indicator
 $key = $param.Remove(0,1)
 if($index + 1 -lt $Params.Count)
 {
 #Check if the next Argument is a parameter
 [string]$param = $Params[$index + 1]
 if($param.StartsWith($ParamIndicator) -ne $true )
 {
 #If it isn’t a parameter then set it as the value
 $value = $param
 $index++
 }
 }
 $Dictionary[$key] = $value
 }#else skip
 }
}

function Validate-IsURL
{
 &lt;#
 .SYNOPSIS
 Validates if input is an URL

 .DESCRIPTION
 Validates if input is an URL

 .PARAMETER Url
 A string containing an URL address

 .INPUTS
 System.String

 .OUTPUTS
 System.Boolean
 #&gt;
 [OutputType([Boolean])]
 param ([string]$Url)

 if($Url -eq $null)
 {
 return $false
 }

 return $Url -match &quot;^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;amp;%\$#_]*)?$&quot;
}

function Get-CMUserApps {
 [CmdletBinding()]
 param
 (
 [Parameter(Mandatory=$True,
 ValueFromPipelineByPropertyName=$True,
 HelpMessage='URL for Application Catalogue')]
 $url,
 [Parameter(Mandatory=$True,
 ValueFromPipelineByPropertyName=$True,
 HelpMessage='Path to logfile')]
 $logfile,
 [Parameter(Mandatory=$True,
 ValueFromPipelineByPropertyName=$True,
 HelpMessage='Temp-file')]
 $temp
 )
 Begin {
 log-write -LogPath $logfile -LineValue &quot;Create web service proxy&quot;
 $catalogurl = $url;
 Log-Write -LogPath $logfile -LineValue &quot;Connecting to $catalogurl&quot;
 try {
 $url = $catalogurl+&quot;/ApplicationViewService.asmx?WSDL&quot;;
 $service = New-WebServiceProxy $url -UseDefaultCredential;

 }
 catch {
 Log-Error -LogPath $logfile -ErrorDesc &quot;AppCatalog no response&quot; -ExitGraceFully $false
 Log-Finish -LogPath $logfilePath -NoExit $true
 break
 }
 }
 Process {

 $total = 0;
 try {
 Log-Write -LogPath $logfile -LineValue &quot;Gathering applications&quot;
 $service.GetApplications(&quot;Name&quot;,$null,&quot;Name&quot;,&quot;&quot;,100,0,$true,&quot;PackageProgramName&quot;,$false,$null,[ref]$total) | select ApplicationId,Name | Export-Clixml $temp
 return $true
 }

 catch {
 Log-Error -LogPath $logfile -ErrorDesc $error[0] -ExitGraceFully $false
 return $false
 }
 Remove-Variable -Name url
 Remove-Variable -Name total
 $service.dispose()

 }

}

function Compare-CMUserApps {
 [CmdletBinding()]
 param
 (
 [Parameter(Mandatory=$True,
 ValueFromPipelineByPropertyName=$True,
 HelpMessage='Permanent-file')]
 $file,
 [Parameter(Mandatory=$True,
 ValueFromPipelineByPropertyName=$True,
 HelpMessage='Temp-file')]
 $temp,
 [Parameter(Mandatory=$True,
 ValueFromPipelineByPropertyName=$True,
 HelpMessage='Path to logfile')]
 $logfile
 )
Process {
 Log-Write -LogPath $logfile -LineValue &quot;Comparing applications lists&quot;
 If (-Not (Test-Path $file)) {
 Log-Write -LogPath $logfile -LineValue &quot;No previous version of apps list&quot;
 try {
 Rename-Item $temp &quot;$prefix apps.xml&quot;
 }
 catch {
 Remove-Item $temp
 Log-Error -LogPath $logfile -ErrorDesc &quot;Unable to create initial list&quot; -ExitGracefully $false
 }

 }
 Else {

 Log-Write -LogPath $logfile -LineValue &quot;Starting check......&quot;
 # $diffs = (Compare-Object -ReferenceObject $(Get-Content $file) -DifferenceObject $(Get-Content $temp)) | Where {$_.SideIndicator -eq '&lt;='}
 # $diffsserver = (Compare-Object -ReferenceObject $(Get-Content $file) -DifferenceObject $(Get-Content $temp)) | Where {$_.SideIndicator -eq '=&gt;'}

 If ((Compare-Object -ReferenceObject $(Get-Content $file -ReadCount 0) -DifferenceObject $(Get-Content $temp -ReadCount 0)) -eq $null) {
 Log-Write -LogPath $logfile -LineValue &quot;No new applications&quot;
 Log-Write -LogPath $logfile -LineValue &quot;Removing temporary file&quot;

 try {
 Remove-Item $temp
 }
 catch {

 Log-Error -LogPath $logfile -ErrorDesc &quot;Unable to remove temp list&quot; -ExitGracefully $false
 }

 }
 Elseif (((Compare-Object -ReferenceObject $(Get-Content $file -ReadCount 0) -DifferenceObject $(Get-Content $temp -ReadCount 0)) | Where {$_.SideIndicator -eq '&lt;='}) -ne $null -and ((Compare-Object -ReferenceObject $(Get-Content $file -ReadCount 0) -DifferenceObject $(Get-Content $temp -ReadCount 0)) | Where {$_.SideIndicator -eq '=&gt;'}) -eq $null ) {
 Log-Write -LogPath $logfile -LineValue &quot;Less applications received&quot;
 try {
 Log-Write -LogPath $logfile -LineValue &quot;Remove permanent list&quot;
 Remove-Item $file
 }
 catch {
 Remove-Item $temp
 Log-Error -LogPath $logfile -ErrorDesc &quot;Unable to remove permanent list&quot; -ExitGracefully $false
 }

 try {
 Log-Write -LogPath $logfile -LineValue &quot;Rename temporary list&quot;
 Rename-Item $temp &quot;$prefix apps.xml&quot;
 }
 catch {
 Log-Error -LogPath $logfile -ErrorDesc &quot;Unable to switch temp-list to permanent&quot; -ExitGracefully $false
 }

 }

 Else {
 Log-Write -LogPath $logfile -LineValue &quot;New applications found&quot;

 $newapps = $true

 }

 }
 If ($newapps -eq $true) {
 return $True

 }

 }

}

function OnApplicationLoad {
 #Note: This function is not called in Projects
 #Note: This function runs before the form is created
 #Note: To get the script directory in the Packager use: Split-Path $hostinvocation.MyCommand.path
 #Note: To get the console output in the Packager (Windows Mode) use: $ConsoleOutput (Type: System.Collections.ArrayList)
 #Important: Form controls cannot be accessed in this function
 #TODO: Add snapins and custom code to validate the application load

 return $true #return true for success or false for failure
}

function OnApplicationExit {
 #Note: This function is not called in Projects
 #Note: This function runs after the form is closed
 #TODO: Add custom code to clean up and unload snapins when the application exits
 #Log-Finish -LogPath $logfilePath -NoExit $true
 $script:ExitCode = 0 #Set the exit code for the Packager
 Log-Finish -LogPath $logfilePath -NoExit $false
 break
}

$AppNotify_Load={
 #TODO: Initialize Form Controls here
 $NotifyIcon.ShowBalloonTip(30000,&quot;New Application&quot;,&quot;You have new applications available&quot;, 'Info')
}

#region Control Helper Functions
function Show-NotifyIcon
{
&lt;#
 .SYNOPSIS
 Displays a NotifyIcon's balloon tip message in the taskbar's notification area.

 .DESCRIPTION
 Displays a NotifyIcon's a balloon tip message in the taskbar's notification area.

 .PARAMETER NotifyIcon
 The NotifyIcon control that will be displayed.

 .PARAMETER BalloonTipText
 Sets the text to display in the balloon tip.

 .PARAMETER BalloonTipTitle
 Sets the Title to display in the balloon tip.

 .PARAMETER BalloonTipIcon
 The icon to display in the ballon tip.

 .PARAMETER Timeout
 The time the ToolTip Balloon will remain visible in milliseconds.
 Default: 0 - Uses windows default.
#&gt;
 param(
 [Parameter(Mandatory = $true, Position = 0)]
 [ValidateNotNull()]
 [System.Windows.Forms.NotifyIcon]$NotifyIcon,
 [Parameter(Mandatory = $true, Position = 1)]
 [ValidateNotNullOrEmpty()]
 [String]$BalloonTipText,
 [Parameter(Position = 2)]
 [String]$BalloonTipTitle = '',
 [Parameter(Position = 3)]
 [System.Windows.Forms.ToolTipIcon]$BalloonTipIcon = 'None',
 [Parameter(Position = 4)]
 [int]$Timeout = 0
 )

 if($NotifyIcon.Icon -eq $null)
 {
 #Set a Default Icon otherwise the balloon will not show
 $NotifyIcon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon([System.Windows.Forms.Application]::ExecutablePath)
 }

 $NotifyIcon.ShowBalloonTip($Timeout, $BalloonTipTitle, $BalloonTipText, $BalloonTipIcon)
}

#endregion

$NotifyIcon_MouseDoubleClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
 #TODO: Place custom script here
 Log-Write -LogPath $logfilePath -LineValue &quot;User clicked icon&quot;
 Log-Write -LogPath $logfilePath -LineValue &quot;Sending user to $appcatalog&quot;
 Start-Process $appcatalog
 $NotifyIcon.Visible = $false

 try {
 Log-Write -LogPath $logfilePath -LineValue &quot;Removing $filepath&quot;
 Remove-Item $filePath
 Log-Write -LogPath $logfilePath -LineValue &quot;Renaming $tempfilePath&quot;
 Rename-Item -Path &quot;$tempfilePath&quot; -NewName &quot;$prefix apps.xml&quot; -Force

 }
 catch {
 Remove-Item $tempfilePath
 Log-Error -LogPath $logfile -ErrorDesc &quot;Unable to remove permanent list&quot; -ExitGracefully $false
 }
 $AppNotify.Close()
 $NotifyIcon.Dispose()
 #Log-Finish -LogPath $logfilePath -NoExit $false
 #$AppNotify.Close()
 #$timer1.Start()

}

$NotifyIcon_MouseClick=[System.Windows.Forms.MouseEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.MouseEventArgs]
 $NotifyIcon.Visible = $true
 $NotifyIcon.ShowBalloonTip(30000,&quot;New Application&quot;,&quot;You have new applications available&quot;, 'Info')
}

$NotifyIcon_BalloonTipClicked={
 Log-Write -LogPath $logfilePath -LineValue &quot;User clicked ballontip&quot;
 Log-Write -LogPath $logfilePath -LineValue &quot;Sending user to $appcatalog&quot;
 Start-Process $appcatalog
 $NotifyIcon.Visible = $false

 try {
 Log-Write -LogPath $logfilePath -LineValue &quot;Removing $filepath&quot;
 Remove-Item $filePath
 Log-Write -LogPath $logfilePath -LineValue &quot;Renaming $tempfilePath&quot;
 Rename-Item -Path &quot;$tempfilePath&quot; -NewName &quot;$prefix apps.xml&quot; -Force

 }
 catch {
 Remove-Item $tempfilePath
 Log-Error -LogPath $logfile -ErrorDesc &quot;Unable to remove permanent list&quot; -ExitGracefully $false
 }
 $AppNotify.Close()
 $NotifyIcon.Dispose()
 #Log-Finish -LogPath $logfilePath -NoExit $true
 #exit
 #$timer1.Start()
}

#Get path which scripts run from
$CurrentPath = Get-ScriptDirectory

#Prefix for all generated files in user's %TEMP%
$prefix = &quot;appsnotify&quot;

#Logfile
$logfilePath = $env:temp+&quot;\$prefix app.log&quot;

$check=Get-Process AppsNotify -ErrorAction SilentlyContinue | Measure-Object

if ($check.count -lt &quot;2&quot;) {

}
else {
 Log-Error -LogPath $logfilePath -ErrorDesc &quot;AppsNotify is already running. Terminating. &quot; -ExitGraceFully $false
 exit
}

#Temporary file to store applications
$tempfilePath = $env:temp+&quot;\$prefix app_temp.xml&quot;
#Permanent file to store applications
$filePath = $env:temp +&quot;\$prefix apps.xml&quot;
#Reset log-file for this session
Remove-Item $logfilePath

################################################################################################################
Log-Start -LogPath $env:temp -LogName &quot;$prefix app.log&quot; -ScriptVersion &quot;2.0&quot;

#Verify that the $CommandLine variable exists
if($CommandLine -ne $null -and $CommandLine -ne &quot;&quot;)
{
 #Log-Write -LogPath $logfilePath -LineValue &quot;There is a command-line&quot;
 Log-Write -LogPath $logfilePath -LineValue &quot;Command-line is:&quot;
 Log-Write -LogPath $logfilePath -LineValue &quot;$CommandLine&quot;
 #$Arguments = Parse-Commandline $CommandLine
 #Convert the Arguments. Use – as the Argument Indicator
 $Dictionary = New-Object System.Collections.Specialized.StringDictionary
 Convert-CommandLineToDictionary -Dictionary $Dictionary -CommandLine $Commandline -ParamIndicator '-'
}
else
{
 #Not running in a packager or no command line arguments passed
 Log-Error -LogPath $logfilePath -ErrorDesc &quot;No command-line argument. Use -appcatalog &lt;url&gt;&quot; -ExitGraceFully $false
 Log-Finish -LogPath $logfilePath -NoExit $false
 break
}

$appcatalog = $Dictionary[&quot;appcatalog&quot;]
if($appcatalog -ne $null -and $appcatalog -ne &quot;&quot;) {
 Log-Write -LogPath $logfilePath -LineValue &quot;Passed Application Catalog is $appcatalog&quot;
 if (Validate-IsURL -Url $appcatalog) {
 Log-Write -LogPath $logfilePath -LineValue &quot;Passed Application Catalog is a URL&quot;
 }
 Else {
 Log-Error -LogPath $logfilePath -ErrorDesc &quot;This is not a url&quot; -ExitGraceFully $false
 Log-Finish -LogPath $logfilePath -NoExit $false
 break
 }

}
else {
 #Address to Application Catalogue
 Log-Error -LogPath $logfilePath -ErrorDesc &quot;We need an Application Catalog&quot; -ExitGraceFully $false
 Log-Finish -LogPath $logfilePath -NoExit $false
 break
}

if ((Get-CMUserApps -url $appcatalog -logfile $logfilePath -temp $tempfilePath) -eq $true) {

 if ((Compare-CMUserApps -file $filePath -temp $tempfilePath -logfile $logfilePath) -eq $true) {

 try {
 $NotifyIcon.Visible = $true
 }
 catch {
 Log-Write -LogPath $logfilePath -LineValue &quot;Exception&quot;
 }
 $NotifyIcon.ShowBalloonTip(30000,&quot;New Application&quot;,&quot;You have new applications available&quot;, 'Info')

 }
 Else {
 Log-Finish -LogPath $logfilePath -NoExit $false
 break
 }

}
Else {
 Log-Finish -LogPath $logfilePath -NoExit $false
 break
}

$NotifyIcon_BalloonTipShown={
 #TODO: Place custom script here
 Log-Write -LogPath $logfilePath -LineValue &quot;Notifying user&quot;
}

} 

Redistribute Failed Packages in ConfigMgr

Since the topic of redistributing failed packages is quite often surfacing in larger environments and there are quite a few PowerShell scripts out there to achieve this.

David O´Brien has written a PowerShell script that redistributes all packages that has any state (but successfull) to all DPs. In a larger environment this would be very risky (consider the amount of bandwidth you could potentially consume).

David went about the task by looking up the current state of the SMS_PackageStatusDistPointsSummarizer which has 7 states of a package , and then looping through all packages for all DPs and initiate the operation RefreshNow for each package and DP.

Within SCCM 2012 R2 there seems to be 9 possible states of a package, where a state 7 and 8 seems to be undocumented. State 7 would indicate that the source-files were not reachable for the SCCM 2012 server, and State 8 would indicate that a package validation failed (for any reason).

Quite often the need is more targeted and in particular we are required to only verify a single package or distribution point. As we would go through the console to check the state of a package and look under Content Status to see – it would be easiest to simply trigger a redistribute action for all DPs that are reported as failed. Previously Greg Ramsey released the great tool to start the action Validate All DPs, which can be initiated from any package under Content Status. Great tool! Lets take that one step further and create two additional menus within Configuration Manager console!

Redistribute a package to all DPs where it failed under Content Status

image

image


Param(
[Parameter(Mandatory=$true)]
[String]$SiteSrv,
[Parameter(Mandatory=$false)]
[String]$SiteNamespace,
[Parameter(Mandatory=$True)]
[String]$PackageID
)

$SiteCode = $SiteNamespace.Substring(14)

Write-Host "Checking" $PackageID -ForegroundColor red
Write-host "Will check for Installed Failed status (3) and Validation Failed (8)"
Write-Host ""

$sOpt = New-CimSessionOption –Protocol DCOM
$SiteServerCIM = New-CimSession -ComputerName $SiteSrv -SessionOption $sOpt

$DPs =  Get-CimInstance -CimSession $SiteServerCIM -Namespace $SiteNamespace -ClassName SMS_PackageStatusDistPointsSummarizer -Filter "PackageID = '$($PackageID)' AND (State = '3' OR State = '8')"
if ($DPs -ne $null)
{
$Count = $DPs |measure
Write-Host "There are $($Count.count) failed DPs at the moment."
Write-Host "Press any key to redistribute..."
Write-Host ""
$a = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
foreach ($dp in $DPs) {
Write-Host "Redistributing $($DP.PackageID) to $($DP.ServerNALPath.Substring(12,7))...." -ForegroundColor Green
try {

Get-CimInstance -CimSession $SiteServerCIM -Namespace $SiteNamespace -ClassName SMS_DistributionPoint -Filter "PackageID='$($DP.PackageID)' and ServerNALPath like '%$($DP.ServerNALPath.Substring(12,7))%'" | Set-CimInstance -Property @{RefreshNow = $true}
}
catch {
Write-Host "Failed to redistribute to $($DP.ServerNALPath.Substring(12,7))" -ForegroundColor Red
}

}
Write-Host "Press any key to close window...."
$a = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
else
{
Write-Host "There are no Failed DPs" -ForegroundColor Green
Write-Host "Press any key to close..."
$a = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

}

Remove-CimSession -CimSession $SiteServerCIM 

Create the following XML-file to enable the right-click menu under Content Status. The file should be placed in the following folder;

14214306-59f0-46cf-b453-a649f2a249e1


<ActionDescription Class="Executable" DisplayName="Redistribute to all failed DPs" MnemonicDisplayName="Redistribute to all failed DPs" Description = "Redistribute to all failed DPs" RibbonDisplayType="TextAndSmallImage">
<ShowOn>
<string>ContextMenu</string>
<string>DefaultHomeTab</string>
</ShowOn>
<Executable>
<FilePath>PowerShell.exe</FilePath>
<Parameters>-Executionpolicy bypass -nologo -WindowStyle normal -command "&amp; 'C:\PowerShellScripts\RedistFailed.ps1' '##SUB:__Server##' '##SUB:__Namespace##' '##SUB:PackageID##'" </Parameters>
</Executable>
</ActionDescription>

To enable a second menu under Distribution Point Configuration Status you can use the following script;

image

image


Param(
[Parameter(Mandatory=$true)]
[String]$SiteSrv,
[Parameter(Mandatory=$false)]
[String]$SiteNamespace,
[Parameter(Mandatory=$True)]
[String]$Server
)

$SiteCode = $SiteNamespace.Substring(14)

Write-Host "Checking" $Server -ForegroundColor red
Write-host "Will check for all failed packages (NOT State 0)"
Write-Host ""

$sOpt = New-CimSessionOption –Protocol DCOM
$SiteServerCIM = New-CimSession -ComputerName $SiteSrv -SessionOption $sOpt
try {
$pkgs =  Get-CimInstance -CimSession $SiteServerCIM -Namespace $SiteNamespace -ClassName SMS_PackageStatusDistPointsSummarizer -Filter "ServerNALPath like '%$($Server)%' AND (State != '0')"

if ($pkgs -ne $null)
{
$Count = $pkgs |measure
Write-Host "There are $($Count.count) failed packages at the moment."
Write-Host "Press any key to redistribute..."
Write-Host ""
$a = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
foreach ($pkg in $pkgs) {
Write-Host "Redistributing $($pkg.PackageID) to $($Server)...." -ForegroundColor Green
try {
Get-CimInstance -CimSession $SiteServerCIM -Namespace $SiteNamespace -ClassName SMS_DistributionPoint -Filter "PackageID='$($pkg.PackageID)' and ServerNALPath like '%$($Server)%'" | Set-CimInstance -Property @{RefreshNow = $true}
}
catch {
Write-Host "Failed to redistribute to $($pkg.ServerNALPath.Substring(12,7))" -ForegroundColor Red
}

}
Write-Host "Press any key to close window...."
$a = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
else
{
Write-Host "There are no Failed pkgs" -ForegroundColor Green
Write-Host "Press any key to close..."
$a = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

}

Remove-CimSession -CimSession $SiteServerCIM

}
Catch {
Write-Error "Failed to query $($Sitesrv)"
Remove-CimSession -CimSession $SiteServerCIM
} 

To enable the right-click menu, create a new XML-file under the following folder;

d8718784-99d5-4449-bc28-a26631fafc07

Content;

<ActionDescription Class="Executable" DisplayName="Redistribute all failed Pkgs" MnemonicDisplayName="Redistribute all failed Pkgs" Description = "Redistribute all failed Pkgs" RibbonDisplayType="TextAndSmallImage">
<ShowOn>
<string>ContextMenu</string>
<string>DefaultHomeTab</string>
</ShowOn>
<Executable>
<FilePath>PowerShell.exe</FilePath>
<Parameters>-Executionpolicy bypass -nologo -WindowStyle normal -command "&amp; 'C:\PowerShellScripts\redistfailedpkgstodp.ps1' '##SUB:__Server##' '##SUB:__Namespace##' '##SUB:NAME##'" </Parameters>
</Executable>
</ActionDescription> 

You can download the scripts from here, but you need to copy the XML-files into the folder of the Admin-Console yourself to make them visible.

Location;

c:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\XmlStorage\Extensions\Actions

ConfigMgr 2012, App-V and /EXE

Really? Still not supported?
When using the ConfigMgr integration – it takes over the App-V Client and replaces the default startup for all virtualized applications to something other than sfttray.exe. No harm in that – apart from the fact that any other switch than /launch is not passed on and will result in – nothing!

Why is this important? The App-V team introduced the most wonderful switch /EXE which is explained a lot better by Aaron Parker in his App-V FAQ. (really – he explains it a lot better than I ever could).

How do you work around it?

Command-line that doesn’t work;
C:\Windows\CCM\VAppLauncher.exe /EXE cmd.exe /launch “Microsoft Expression Encoder 4 4.0.4276.0

Command-line that will work with any App-V Client configuration;
“C:\Program Files\Microsoft Application Virtualization Client\sfttray.exe” /EXE cmd.exe /launch “Microsoft Expression Encoder 4 4.0.4276.0

In ConfigMgr 2012 – any produced file type association and shortcut will using vapplauncher.exe as the starting point. To get the latter possibility to troubleshoot within the virtual environment – create a shortcut for SFTTRAY.EXE located in the installation folder of the App-V Client and append the /EXE and /launch switches with your application name and version as input (written in italics above).

Config Mgr 2012 and App-V

Previously when using App-V and leveraging the infrastructure of SCCM 2007 and the integration of the App-V you were “limited” to using anything from Configuration Manager. In itself – this wasn’t all that bad, but any type of migration scenario were very much dreaded and the enabling or disabling of the integration was a big on-off switch – that dropped anything previously distributed to App-V Client and took over the control of the client. Configuration Manager 2012 has come a long ways and especially when migrating between different scenarios – it isn’t as controlling in some scenarios.

First – a few notes; There isn’t any configuration on the Configuration Manager 2012 infrastructure or client that enables or disables any functionality. By default – its capable of integration with App-V and will do so without any particular tasks required to be performed by the administrator. Reviewing a previously installed App-V Client that has one single application distributed gives us the following views;

(registry configuration to start an application)

image

(one application distributed using stand-alone method)

image

Even though this wasn’t part of the experiments conducted today – any Publishing Servers should be removed once the client controlled by Configuration Manager 2012. Not only once – but it should continuously be removed in the future.

After installing Configuration Manager client, adding applications and creating new deployment (that didn’t require the install – so we could control the time of installation) for our device – the above registry keys didn’t change. Once we initiated the deployment and installed the App-V package – the registry keys switched to the below;image

Interesting part is that the old application is still there and will still start (as opposed to using the integration with Configuration Manager 2007 – where it would not start). What was rather surprising though – is that even though all three applications are started, the one not distributed via Configuration Manager 2012 will never say that it is In Use.

image