Never ending reboot prompts

A few servers had continue to request, through the ConfigMgr, a reboot after the servers were, surprise, rebooted due to Windows Updates.

Use the below PS code you can see the current state of the reboot.

Invoke-WmiMethod -Namespace “ROOT\ccm\ClientSDK” -Class CCM_ClientUtilities -Name DetermineIfRebootPending -ComputerName <name>

Obviously, before actually doing any of the below suggestions – a restart should before forced on the client. On the servers in question, a minimum of one reboot per server has been confirmed before using this workaround.

The WMI method apparently only retrieves the current value of the ConfigMgr-client. To change the state of the WMI method -  one has to digest the registry a bit;

clip_image001

The current state is saved in HKLM\Software\Microsoft\SMS\Mobile Client\Reboot Management\RebootData

On a server rename it to old, and then restarted the CCMEXEC-service.

To confirm that a request for a reboot you can either await the GUI initialization, or use the above PS code to verify the pending reboot state.

All Reboot requests are logged in RebootCoordinator.log in the ConfigMgr client log-folder.

Some interest articles;

http://blog.thesysadmins.co.uk/sccm-2012-stopping-your-computer-is-about-to-restart.html

http://blogs.technet.com/b/umairkhan/archive/2014/06/10/configmgr-client-reboot-internals-for-update-deployments.aspx

http://blogs.technet.com/b/heyscriptingguy/archive/2013/06/10/determine-pending-reboot-status-powershell-style-part-1.aspx

ConfigMgr Client installation failure

Two odd failures from the ConfigMgr client that caused some headaches.

StatusAgentProxy.dll fails to register.

Verify that MSVCR100.DLL in c:\windows\system32 (yes, on a 64-bit system aswell) is the correct size.The renamed file (MSVCR100old.dll) shows the size of 755kb – which most likely is installed by a 3-party application and in error replaced the correct version of the file. As you can see, both files have the same version number.

clip_image002

 

CcmRegisterPerfCounter fails with an unexpected error.

The custom action is intended to register the Performance Counters for ConfigMgr client. Basically it needs two files in c:\windows\system32 (ccmframework.h and ccmframework.ini), a few registry keys and then it can set it up. Performance Counters seems to be very stable so only a 3-party application can actually cause any havoc here. To resolve this perform the following;

Open MSCONFIG. Select the Startup-tab and click disable all.

clip_image002[5]

Select the Services-tab. Select to hide all Microsoft-services and then click the Disable all.

clip_image002[7]

Restart the computer, and verify that the installation will complete.

Sequence Ecowin Pro 6

This is a repost of an old-blog article. This still applies to App-V 5.0, and as far as I remember – this content is not published anywhere else.

Since about 2009 and when I first replied to a thread regarding a “common” issue with sequencing a specific software named Ecowin Pro 6, a more or less active pursuit of finding a resolution has been ongoing.

Back in November 2009 Tim Mangan was over having a training session with us and I brought up the issue. Just about the same time the App-v 4.6 RC2 client was released to public and I posted the thread as a bug and got a response. Unfortunately the only response anyone gave was; Yes, its a problem.

Currently a bit jetlagged and trying to convert back normal hours (US v SE). I decided to stay up (landed about 7am and wanted nothing else than to sleep) to revert back the clock to normal – what better task than the above unsolved mystery?

After about 3 hours of various failed attempts and loads of reading among various articles that hopefully would give a hint – something came up. A guy named Jochen had described a solution to making SidebySide local (meaning placing them in the same folder as the application). As the error was similar and related to SxS (see below) a trial has to be made to see if this could possible be the solution. Below is a note from what the Application log showed when attempting to start Ecowin Pro as an app-v package.

Log Name:      Application
Source:        SideBySide
Level:         Error
Keywords:      Classic
Description:
Activation context generation failed for “Q:\ecowin.002\VFS\CSIDL_PROGRAM_FILES\EcoWin\EcoWin.exe”. Dependent Assembly Vinga.vscom90u,processorArchitecture=”X86“,publicKeyToken=”e520fe831c9439c8“,type=”Win32“,version=”1.0.0.55” could not be found. Please use sxstrace.exe for detailed diagnosis.

Jochens approach was to place the needed DLL-file and two manifests referencing it within the same folder as the application.
As I am not a programmer and specifically not the programmer any recompiling was not in question. From my understanding the manifest first created told the executable that it needed an assembly and the second manifest defined where to locate the DLL.

So, first manifest to tell Ecowin where it should get its above Vinga.vscom90u (named ecowin.exe.manifest)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="<strong>win32</strong>" name="<strong>Vinga.vscom90u</strong>" version="<strong>1.0.0.55</strong>" processorArchitecture="<strong>x86</strong>"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>

The second manifest telling Ecowin where VScom90u.dll was (named Vinga.vscom90u.manifest)

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInheritable />
<assemblyIdentity type="<strong>win32</strong>" name="<strong>Vinga.vscom90u</strong>" publicKeyToken="<strong>e520fe831c9439c8</strong>" version="<strong>1.0.0.55</strong>" processorArchitecture="<strong>x86</strong>" />
<file name="<strong>vscom90u.dll</strong>" />
</assembly>

The version, name, type, processorArchitecture, publicKeyToken are all picked up from the original error message. Cross-referencing those with what is captured within the package the following file was located – the folder named contains the file, the version, the processer architecture and that seems to fit pretty good (and the file name seems to ring a bell..)

 

That file was now copied from its original folder to the same folder as the manifests and the Ecowin.exe

Wrapping up my project and deploying it to the client gave me some hope as a splash-screen showed up – only to reveal the next error message;

<lost screenshot>

For some reason the following registry key came to mind;

<lost screenshot>

Apparently the software notes what operating system it was installed on – just altering the above to (601) gave a new error message;

<lost screenshot>

Extracting the detailed message the following shows

06/28/10 15:36:38 Class not registered [VSCoCreateInstance(progid=EWDBINET2.MtickSeriesListInet2,clsid={EB702F7B-DF8E-4E4D-AE7D-BCC0E48E7105},iid={00000000-0000-0000-C000-000000000046}),CMTickApp::CheckDefaultDB]

So – something is wrong with the datasource specified. Reviewing a process monitor log a reference can be found

<lost screenshot>

Not really knowing the application – this is where some insightful answers would be needed. The error is very application specific and what relevance it has (or not) could perhaps be guessed from the 136 000 process monitor log seen above or simply asked to an application expert.

If anyone cares to explain the above to me – it would be greatly appreciated. I figured out a workaround using some experience, wild guessing and lots of reading.

Visual Studio 2013 silent install

Visual Studio 2013 is now available through Microsoft Volume Licensing Website, and can also be downloaded through Developer Network.

Directly from the Visual Studio-website you can find all editions with the latest Update 3 slipstreamed into a single media, however if you visit the Microsoft Volume Licensing Website there is only the RTM version of Visual Studio 2013 available. The major difference between the Visual Studio-website and the MVLS-website is that the license is embedded within the downloaded media you retrieve from MVLS. The files available from Visual Studio-website is a 90-day trial version. If you press the Key-option at MVLS no product key is presented to you.

So, if you want to deploy Visual Studio 2013 with the latest (or any) update? Do the following!

Downloaded Visual Studio 2013. Technically we are not interested in the bits, but when the download is started a product key is generated with the download link

Downloaded the latest ISO-file from the edition of Visual Studio 2013 (Professional perhaps?)

image

Once the ISO-files is downloaded (weighs in at about 6gb), extract the file.

Do not read the guide from Developer Network on howto deploy Visual Studio in an unattended manner (well, ok – if you really want to). Instead start the vs_<edition>.exe file with a question mark. Like this

vs_professional.exe /? 

The following output is generated;

Setup - Usage

This setup supports the following switches:

/Help              Display this usage text.
/H
/?

/Quiet             Quiet mode with no display and no user interaction.
/Q
/Silent
/S

/Passive           Display progress but do not wait for user input.

/PromptRestart     Prompt the user before restarting the system.

/NoRestart         Do not restart during or after installation.

/ForceRestart      Always restart the system after installation.

/Log               <filename> Specifies a location for the log file.
/L

/Uninstall         Uninstall the product.
/U

/Uninstall /Force  Uninstall the product and features shared with other
products.
/U /Force          Warning: using this switch may cause other products i
nstalled on this machine to stop functioning properly.

/Repair            Repair the product.

/Layout            Create a copy of the media in specified folder.

/NoRefresh         Prevent setup checking for updates from the internet.

/NoWeb             Prevent setup downloading from the internet.

/Full              Install all product features.

/AdminFile         <filename> Specifies the installation control file.

/AddRemoveFeatures Choose which features to add or remove from the insta
lled product.

/CustomInstallPath <path>
Set Custom install location

/ProductKey        <25-character product key>
Set custom product key (no dashes)

For more information see <a href="http://go.microsoft.com/fwlink/?linkid=376912&c">http://go.microsoft.com/fwlink/?linkid=376912&c</a>
lcid=0x409 

A sample command-line for silent install would be;

vs_professional.exe /Q /S /LOG %SYSTEMROOT%\TEMP\VS_2013_U3.log /NoWeb /NoRefresh /Full /ProductKey XXXX-XXXX-XXXX-XXX

Office 365 / 2013 and App-V – Exclude apps

With the latest release (June 5) of Office Deployment Tool there is the ability to exclude applications when creating a package. For example, if you don’t want to deploy – say Lync? – even though you are technically licensed for it.

How does it work?

Create your XML-file

The XML defines what product you want to deploy / create an App-V package for.

A reference can be found on Technet, with the entire list of all applications that can be excluded. Do note that each application you want to exclude is a new line within the XML-file

image

<Configuration>

<Add SourcePath="c:\media\" OfficeClientEdition="32" >
<Product ID="O365ProPlusRetail">
<Language ID="en-us" />
<ExcludeApp ID="Access" />
<ExcludeApp ID="InfoPath" />
<ExcludeApp ID="Lync" />
</Product>
</Add>
<Display Level="None" AcceptEULA="TRUE" />
<Property Name="AUTOACTIVATE" Value="1" />
</Configuration>

Run the command-line

Download source media;

 setup.exe /download c:\media\configuration.xml

Create the App-V package;

setup.exe /packager c:\media\configuration.xml c:\media\package

Now you have a package!

Just to deploy!

Remember, Office is only supported to be deployed as a global package when using App-V

 

Read more about this on Technet!

App-V 5 clean uninstall

Did you uninstall an App-V 5 client, and expected that it would cleanup after the mess it created?

Well, not all the time. After some interesting encounters – here is a small cleanup-list;

Remove the following folders;

C:\Program Files\Microsoft Application Virtualization

C:\ProgramData\App-V

C:\ProgramData\Microsoft\App-V

Remove the following registry keys;

HKLM\Software\Microsoft\AppV

Reinstall, and hopefully you are good to go!

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

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

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{
 <#
 .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 "C:\Windows\Temp" -LogName "Test_Script.log" -ScriptVersion "1.5"
 #>

 [CmdletBinding()]

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

 Process{
 $sFullPath = $LogPath + "\" + $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 "***************************************************************************************************"
 Add-Content -Path $sFullPath -Value "Started processing at [$([DateTime]::Now)]."
 Add-Content -Path $sFullPath -Value "***************************************************************************************************"
 Add-Content -Path $sFullPath -Value ""
 Add-Content -Path $sFullPath -Value "Running script version [$ScriptVersion]."
 Add-Content -Path $sFullPath -Value ""
 Add-Content -Path $sFullPath -Value "***************************************************************************************************"
 Add-Content -Path $sFullPath -Value ""

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

 }
}

Function Log-Write{
 <#
 .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 "C:\Windows\Temp\Test_Script.log" -LineValue "This is a new line which I am appending to the end of the log file."
 #>

 [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{
 <#
 .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 "C:\Windows\Temp\Test_Script.log" -ErrorDesc $_.Exception -ExitGracefully $True
 #>

 [CmdletBinding()]

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

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

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

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

Function Log-Finish{
 <#
 .SYNOPSIS
 Write closing logging data & 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 "C:\Windows\Temp\Test_Script.log"

 .EXAMPLE
 Log-Finish -LogPath "C:\Windows\Temp\Test_Script.log" -NoExit $True
 #>

 [CmdletBinding()]

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

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

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

 #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
{
<#
 .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
#>

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

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

 if($CommandLine)
 {
 #Find First Quote
 $index = $CommandLine.IndexOf('"') 

 while ( $index -ne -1)
 {#Continue as along as we find a quote
 #Find Closing Quote
 $closeIndex = $CommandLine.IndexOf('"',$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('"',$index + 1)
 }
 }
 return $Arguments
}

function Convert-CommandLineToDictionary
{
 <#
 .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 '-'
 #>
 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 = ""
 $value = "" 

 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
{
 <#
 .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
 #>
 [OutputType([Boolean])]
 param ([string]$Url)

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

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

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 "Create web service proxy"
 $catalogurl = $url;
 Log-Write -LogPath $logfile -LineValue "Connecting to $catalogurl"
 try {
 $url = $catalogurl+"/ApplicationViewService.asmx?WSDL";
 $service = New-WebServiceProxy $url -UseDefaultCredential;

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

 $total = 0;
 try {
 Log-Write -LogPath $logfile -LineValue "Gathering applications"
 $service.GetApplications("Name",$null,"Name","",100,0,$true,"PackageProgramName",$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 "Comparing applications lists"
 If (-Not (Test-Path $file)) {
 Log-Write -LogPath $logfile -LineValue "No previous version of apps list"
 try {
 Rename-Item $temp "$prefix apps.xml"
 }
 catch {
 Remove-Item $temp
 Log-Error -LogPath $logfile -ErrorDesc "Unable to create initial list" -ExitGracefully $false
 }

 }
 Else {

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

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

 try {
 Remove-Item $temp
 }
 catch {

 Log-Error -LogPath $logfile -ErrorDesc "Unable to remove temp list" -ExitGracefully $false
 }

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

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

 }

 Else {
 Log-Write -LogPath $logfile -LineValue "New applications found"

 $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,"New Application","You have new applications available", 'Info')
}

#region Control Helper Functions
function Show-NotifyIcon
{
<#
 .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.
#>
 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 "User clicked icon"
 Log-Write -LogPath $logfilePath -LineValue "Sending user to $appcatalog"
 Start-Process $appcatalog
 $NotifyIcon.Visible = $false

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

 }
 catch {
 Remove-Item $tempfilePath
 Log-Error -LogPath $logfile -ErrorDesc "Unable to remove permanent list" -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,"New Application","You have new applications available", 'Info')
}

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

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

 }
 catch {
 Remove-Item $tempfilePath
 Log-Error -LogPath $logfile -ErrorDesc "Unable to remove permanent list" -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 = "appsnotify"

#Logfile
$logfilePath = $env:temp+"\$prefix app.log"

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

if ($check.count -lt "2") {

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

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

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

#Verify that the $CommandLine variable exists
if($CommandLine -ne $null -and $CommandLine -ne "")
{
 #Log-Write -LogPath $logfilePath -LineValue "There is a command-line"
 Log-Write -LogPath $logfilePath -LineValue "Command-line is:"
 Log-Write -LogPath $logfilePath -LineValue "$CommandLine"
 #$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 "No command-line argument. Use -appcatalog <url>" -ExitGraceFully $false
 Log-Finish -LogPath $logfilePath -NoExit $false
 break
}

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

}
else {
 #Address to Application Catalogue
 Log-Error -LogPath $logfilePath -ErrorDesc "We need an Application Catalog" -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 "Exception"
 }
 $NotifyIcon.ShowBalloonTip(30000,"New Application","You have new applications available", '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 "Notifying user"
}

} 

Adobe PDF Addon download

Previously I discussed the deployment of Adobe PDF Addon with a virtualized instance of Adobe Acrobat. The Adobe PDF Addon is also known as the Adobe PDF Printer or the Adobe Distiller. In the end – its a piece of software that contains a driver and therefore can not be virtualized.

Extracting this from a generic piece of Adobe Acrobat media is rather painful, if at all possible, however the Adobe Distiller (aka Adobe PDF Addon) is available as a standalone installer.

How would one retrieve this standalone installer?

Well, by an odd-chance I bypassed the Creative Cloud Packager and downloaded the Adobe FrameMaker 12 from the Adobe Licensing Website. Hidden within these source-files there is a folder named;

AdobePDFCreationAddOn11_x86_x64

image

There are a few things needed to silently install this msi (distillr.msi).

Visual C++ 2010 SP1 (x64) is a prerequisite for the application.

There is a check by the installer to ensure that it is not installed standalone. Within the InstallExecuteSequence table the following CustomAction-reference needs to be removed;

image

With the above in place – you are all set togo!

1 2 3 15