ConfigMgr, apps and OSX

ConfigMgr offers limited support to manage an OSX-device, however you can extend the capabilities using third-party tooling such as Parallels Mac Management for SCCM.

Parallels Mac Management for SCCM primarily offers something similar to Software Center – namely an interface called Parallels Application Portal that will allow available applications (not the classic package) to be installed or removed through a user interface. Unlike the Windows-applications and their respective deployment types for an application the ability to configure the deployment is rather peculiar.

Some basics

You will need access to an instance of OSX where you can generate the package. As previously described on howto deploy Symantec Endpoint Protection there is a small tool to generate a ConfigMgr compatible package from various different formats available for OSX to install applications.

Once the package is generated it will contain the binary pieces of the application, aswell as a basic command-line to install the application and (optional, but enabled by default) a detection method to verify that the application is installed.


Most likely the command-line will look something like this

/usr/sbin/installer -pkg "Parallels Desktop.pkg"  -target "/" –verboseR

As Parallels Mac Management for SCCM has the ability to also uninstall applications there is a need to specify the command-line for actually removing the application. Using the “:” you can provide the two commands on the same command-line.

Uninstall command-line (more about rm on Mac Developer Library)

rm -rf "/Applications/Parallels"

Combined command-line (notice the : at the start, the middle and the end of the command-line):

:/usr/sbin/installer -pkg "Parallels Desktop.pkg"  -target "/" -verboseR:rm -rf "/Applications/Parallels":

Detection Method

Detection Method will be determined by default (you will need to specify the –c option to avoid having it generated within the ConfigMgr package) when a package is created.


The Package ID can be reviewed within OSX using the pkgutil.

Sample command-line to review if a package is installed or not within OSX

$ pkgutil --pkg-info com.parallels.package

Sample output

$ pkgutil --pkg-info
volume: /
location: /
install-time: 1306707387
$ date -r 1306707387
Sun May 29 15:16:27 PDT 2011

As you can see we request information if a specific package is installed. To list all packages on a volume you can use the following command

$ pkgutil --packages –volume /

Additional command-line

During the uninstall command we technically only remove the application, however the information which we base the detection on is left behind. To remove the package information the following command-line can be used

$ pkgutil --forget com.parallels.package

Using && (and) we can combine our multiple commands for uninstall – both removing the Parallels and the package information.

:/usr/sbin/installer -pkg "Parallels Desktop.pkg"  -target "/" -verboseR:rm -rf "/Applications/Parallels" && pkgutil --forget com.parallels.package:


Unlike the Windows-applications where the install / uninstall scenario are split into two separate command-lines there is a single-command line to handle for OSX. One needs to be familiar with OSX tools to manage applications and the terminal capabilities to successfully manage a successful installation and uninstall of an application.

ConfigMgr client–install and maintenance

After going down a rabbit hole attempting to understand the process of why ConfigMgr clients fail. Fail to install, fail to keep themselves running, failure to repair themselves – there has been a realization that a lot of people have spent an insane amount of time doing the same thing. Oddly enough, very few people seem to understand the implications of doing things one way or another.

Hopefully this can be a summary post that will detail the implications of choosing different paths when installing the ConfigMgr client.


There is an extensive documentation of what the technical requirements are of the platform you intend to install the ConfigMgr client on. Most likely this will not be any major issue as the majority of these will be fulfilled on any supported platform and if they are not available prerequisite will be installed as part of the very stable CCMSetup.exe.

ConfigMgr setup (called ccmsetup.exe) is a wrapper executable that will leverage the contents of The .CAB-file contains a XML file that will detail all prerequisite checks that should be performed on a client and attempt to verify that they are installed, and if not download them (from a source-repository defined in many ways – if none is specified the folder which ccmsetup is started from will be the source)  and install them.

Can this cause issues? Yes. If the folder where ccmsetup.exe is called from does not contain the prerequisites specified in so they actually can’t be downloaded or the alternative source-folders specified doesn’t contain them the installation will fail. Or worse, if the requirements are invalid this will also cause issues. An invalid hash for SCEPInstall.exe was the culprit of an update released by Microsoft or the publically acknowledged MicrosoftPolicyPlatformSetup.msi invalid signature has also been the cause of headaches.

So lets clarify;

Don’t remove things from the ConfigMgr client source-folder.




When ccmsetup.exe is started it is a non-interactive process which only allows anyone to review the progress by reading log-files. For the impatient that hopes to have a progress-bar running – there is none. (log files located in c:\windows\ccmsetup\logs)

CCMSetup.exe has some intelligence to avoid have someone firing of the non-interactive process and fail to properly install the ConfigMgr client. It uses the Active Directory (in multiple ways) to identify most of the settings that are required, however of course there are many optional and beneficial options.

Install Properties

There is a small rule when it comes to passing parameters to ccmsetup.exe
Parameters that start with a / is dedicated for the ccmsetup.exe-wrapper itself, whereas the parameters without (and usually written in capital letters) are dedicated for the MSI and used by the installation process. Parameters meant for the ccmsetup.exe (with /) should be written first and before all parameters meant for the client MSI.

So, the client will do the following to identify how it should be installed:

Parse commandline

Ccmsetup command line: "C:\WINDOWS\ccmsetup\ccmsetup.exe" /runservice "SMSCONFIGSOURCE=P" "smssitecode=P01"

Read from registry

Command line parameters for ccmsetup have been specified.  No registry lookup for command line parameters is required.

Query Active Directory

Performing AD query: '(&(ObjectCategory=mSSMSManagementPoint)(mSSMSDefaultMP=TRUE)(mSSMSSiteCode=P01))'

Parameters published within Active Directory is defined from the ConfigMgr environment when modifying the Site-settings



So, as long as you have a fairly uncomplex environment of ConfigMgr the installation of the ConfigMgr client will be simple and you can pass the installation files to anyone without a lot of instructions. Just double-click this file (ccmsetup.exe) and the settings will be retrieved from Active Directory, at minimum.

Download of installation-files

If there are multiple offices and perhaps even some limited bandwidth scenarios with high-latency a quick question comes to mind – how can we optimize this?

As previously noted the most common scenario is that the client itself and the prerequisites will download themselves from the folder they were started into a local-cache folder named ccmsetup. Technically, the ccmsetup will just perform enough actions to get this download started and then handover to the local process that will continue to download the of remaining prerequisites and client-files as illustrated by the log-file.


Once the local file exists (along with the real (now local) installation-process will continue the installation. This will continue to download the remaining bits and pieces from either the source-folder or an alternative source-folder:


There can be multiple source-folders specified and before it attempts to use one the ccmsetup.exe will verify that the source-folder can be reached – if not it will invalidate it.

Technically, the commandline can look like this

ccmsetup.exe /source:\\validfileshare\client<em> </em>/source:<em>\\invalidfileshare\client </em>/source:<em>\\validfileshare2\client


The folder used will be based on availability (must be reachable) and the order in which they were listed. There is no intelligence that calculates latency or bandwidth, the only check performed is if the files are reachable. However, if you want that to be used you can leverage the ConfigMgr infrastructure using the property /mp.


MP stands for Management Point and this specific property will only define what Management Point CCMSETUP will contact to retrieve the client source-files. The property will not be used by the ConfigMgr client itself (you can use a different property for this). The Management Point will leverage the ConfigMgr package created during its setup (called Configuration Manager Client Package) and all of the ConfigMgr infrastructure. Based on the boundaries defined and what Distribution Point will serve those boundaries it will download the package from the best local (read Fast boundary) Distribution Point, and if none are available it will fallback to a remote one (read slow).

Sample review of the earlier stages of an CCMSetup running where it is contacting the Management Point for content download.


The download from a Distribution Point will leverage BITS and the priority of that download can be specified using the BITS priority property.




ConfigMgr is a living product and therefore there are lots of released updates for it. ConfigMgr 2007 had a set of specific solutions (hotfixes) released that included updates to the client and every once in a while these were gathered up in a Service Pack or Release Pack (SP or R) which provided a brand new MSI-file.ConfigMgr 2012 still has the concept of Service Pack and Release Pack, however very few hotfixes have been released in between these releases and instead they are release in cumulative packs that will provide a new MSP-file with all the fixes from backwards.

Great improvement that ConfigMgr 2012 has a single Microsoft Patch to be installed. Of course there is a desire to ensure that the clients receives these fixes as soon as possible and if possible to ensure that at installation-time the patch is applied. This is especially important during Operating System Deployment as the fixes may resolve issues during the Operating System Deployment process.

Patches introduces the first challenge when it comes to ensuring that a client will install properly.

Supported method

Microsoft has recommended the approach of using the generic Windows Installer-property PATCH. This has been around as a recommended method since SMS 2003, and continue to be the official recommended way forward.

There are a few caveats to using the PATCH property and that is namely the limitation of referencing any type of file there.

File path has to be an absolute path and no variables can be used when the command-line is parsed by Windows Installer. If the file referenced does not have the extension MSP it will be ignored. In essence: You have to write the specific path where the file is located in plain old english (or.. well..)

Microsoft has therefore provided great blog-articles (no technet-article as the property is a generic Windows Installer property) on howto leverage this for the published Client Installer Properties and multiple ways of using this property during the Operating System Deployment process.

Applying a ConfigMgr Hotfix During the Client Installation of an OS Deployment

Patch ConfigMgr 2012 x86 and x64 clients during a task sequence using the PATCH property

Deploying ConfigMgr Hotfixes

There is one key take away that is worth noting and that is the fact that using the PATCH property will install all the bits of the new patch during the initial installation of the client and once the installation is completed the client will be at the latest build of the client. If applying the patch post-installation it is required to restart the ConfigMgr client (we want to release any locks to a file) before we have actually applied the update.

Primary concept here are based into a few parts that will provide us with a few different ways of populating the PATCH property depending on when and how we install the MSP.

Install on existing Windows machines

When using the published installer properties (used for client push scenarios, manually triggered installs without any overriding options) we will reference the patch using a UNC-path. The UNC-path can reference any file-share that the computer account has access to. Sample PATCH-property


Using a UNC-path of course is a hard-coded reference to a specific file-share (and using DFS you can of course offer some abstraction here), however there is a concern to be raised with this method.

Referencing a UNC-path would of course require that the client has access to the MSP-file on the occasion that a future repair would happen or during the installation. If the file would not be reachable (move, network disconnect, permissions improperly configured) the installer with exit with the generic exit code of 1635. Even worse though is the complete failure of the installation due to a high-latency / low bandwidth scenario where the file-copy operation just fails. Sample error code in such a scenario:

MSI: Action 1:44:18: InstallFiles. Copying new files    ccmsetup    2015-08-02 01:44:18    8560 (0x2170)
MSI: Internal Error 2902. ixfAssemblyCopy    ccmsetup    2015-08-02 01:44:20    8560 (0x2170)
MSI: Action 1:44:21: Rollback. Rolling back action:    ccmsetup    2015-08-02 01:44:21    8560 (0x2170)

Install during Operating System Deployment

During OSD there seems to be two ways to reference the patches. The previous (circa 2007 and a bit of the life-span of 2012 in) way was to reference the patches in their respective packages when downloaded by ConfigMgr during the OSD-process. A sample path has been expressed in the first article. The PATCH would reference a folder, temporarily, created by a specific package and could install the patch with local access.


During later articles there is a new recommended method of running a pre-step to copy the file into a local-folder (c:\windows\temp anyone?) and then referencing this folder instead


Briefly looking over these methods we have a few things worth noting;

1. We do not have the ability to leverage a single way of referencing these patches if we were to follow Microsoft practices. UNC-paths for one method and a local-copy for a different method?

2. We can not leverage the ConfigMgr infrastructure when downloading patches since we are forced to reference them using a hardcoded path during a manual install / push-scenario

3. Patch installation from a UNC-path may be high-risk as any loss of network connectivity or if the connection is less than perfect may result in a complete failure of the client install. Preferred method (which is most likely this is the new recommended approach for OSD) would be to download a copy to the local harddrive on the client.

4. There is no dynamic way of placing the patches in a single location and have the ConfigMgr client automatically apply them. We have a hardcoded reference to the patch location, and in addition we also have a hardcoded reference to what patch we are deploying.

Now, remember all these cons as more will be remarked later.

Unsupported method

Since SMS 2003 there has been some legacy code that will allow the CCMSetup to detect a specific folder, download any patches located in this folder and then post installation also install the patches.

The folder is called ClientPatch (surprise!) and should be created in each architecture directory.


Once the installation is started and detects that this folder is located in the source-folder it is currently using it will download the file



After the installation of client.msi is completed it will continue to install any downloaded MSP-files.



This method is unsupported and there is no testing performed by the ConfigMgr team if any changes they make will potentially cause any future issues. There is a lot of public ‘it works without issues’ and some people saying ‘we have identified issues’.

People outside of Microsoft claims that it´operates without issues.

SCCM 2012 Client Push – Including hotfixes / CU patches in Client Push

System Center 2012 Configuration Manager R2 Automating Client Patch installation during Client Push Install with CU2

ClientPatch folder method for including client updates – supported?

Change in how to apply Patch with SCCM Client push



Issues that have been identified relate to specific properties not beeing parsed properly is using this approach as the installation order is to setup the client, and then after the client is installed it will automatically install the patch. As the patch installation must stop all services (think ConfigMgr agent, but also the Task Sequence service) to release any locks to files that it wants to update the loss of properties seems harmless (and yet also a logical side-effect) in comparison.

As you might guess; Stopping the Task Sequence service during Operating System Deployment is a high-risk undertaking.



Configuration Manager has introduced several mechanisms to ensure a ConfigMgr client stays healthy. One part of this, which has been remarked early on, is the CCMEval scheduled task.


The CCMEval performs two tasks namely to identify (task one) and remediate (task two) unhealthy clients. The name is a give-away. There is an option to configure it to only perform the first (either through a property at install-time or as a post-configuration task) which of course will only notify you as an administrator through the ConfigMgr console. And by notify we mean that you have to actively dig out the information under Monitoring –> Client Status.


CCMEval’s doings (or undoings) can be identified through the log-file named ccmsetup-ccmeval.log located in C:\Windows\ccmsetup\Logs.

Identify task

Lets run through its doings when performing a health check to identify a unhealthy client

First we can see that it triggers the ccmsetup with the parameter /evaluate and focusing only on prerequisites. Most likely this will identify that all necessary prerequisites are installed, available for installation if they are missing and if not it should trigger an installation of the missing prerequisites.

As you can see it remembers the command-line we used for installing the client. A validation will also happen (just like it does during install-time) that source-folders are validated.


The previously used command-line for installation is saved away in the registry. As far as the investigation goes this little vital component is saved in the below registry-key.


Once it has validated the source the client knows about (remember; working folder, /source and /mp are valid options to specify this during the initial installation) it will start to digest the information that it knowns about the prerequisites. Just as the initial installation it will extract the XML-file (now using the local-copy) from and then proceed to ensure what prerequisites are applicable, available and installed.


Once the prerequisites had their run there is an secondary run that will validate the client itself


The result of this (and what checks are performed to ensure that a healthy ConfigMgr client is on the machine) is logged in a secondary file located in C:\Windows\CCM\Logs and its named (surprise!) ccmeval.log



Remediate Task

The above portion will only identify the current state of a client and if configured as such – the CCMeval will stop at the above part and inform (notify) the ConfigMgr hierarchy that this is the case. By default it is also enabled to remediate a client which is considered unhealthy.

One of the more harmless remediation is to just start the CCMExec-service. Fairly easy right? Serivce not running? Start it.

One of the more challenging remediation is to reinstall either the prerequisites or even the client in itself.

Wait. What? why would this be a challenge? Lets play out two potential scenarios that are very likely to happen if following some articles that have been posted above.

Scenario #1

You followed the suggestion to ensure that the ConfigMgr 2012 hotfixes are installed during the Task Sequence that deploy the operating system using the package that contains the ConfigMgr Client. A sample PATCH-property would be seen like this:


As this is a temporary location for storing packages only during the Task Sequence execution once a Task Sequence has completed the folder is removed.
If the client were to become faulty due to <insert any reason> a repair would be attempted using the initial command-line as found in the registry (HKLM\Software\Microsoft\CCMSetup) such as this

"/runservice" "/source:\\fileshare\validsource" "SMSCONFIGSOURCE=P" "smssitecode=P01" "" "PATCH="C:\_SMSTaskSequence\OSD\XXX00002\x64\hotfix\KB2905002\Configmgr2012ac-r2-kb2905002-x64.msp"  "

However, this installation will fail with a generic error code of 1635 due to the installer being unable to locate the previously temporary folder. The patch is no longer in the above location.

Scenario #2

During a client push scenario we have published the UNC-path where our patch-files are located on a file-share within our environment. This could be on the site-server, a DFS-root or somewhere else.

Sample property


There are two potential concerns that may cause harm here if a repair ever were to be triggered. If an admin was haggled to cleanup, save disk space or perform any similar action a year later a scenario will arise where the patch is not available. Perhaps some a new way of distributing the hotfix was discovered and it was decided to cleanup old file-shares. Well, if the above patch was no longer available at the above location all future repairs would halt with a generic 1635 error.

The other concern raised is if the file is still available however the client is constrained by high latency. Perhaps some packet drops. Well, perhaps the connection might be congested with Channel9 video streams. During the patch file-copy part the following error is very likely to happen

MSI: Action 1:44:18: InstallFiles. Copying new files    ccmsetup    2015-08-02 01:44:18    8560 (0x2170)
MSI: Internal Error 2902. ixfAssemblyCopy    ccmsetup    2015-08-02 01:44:20    8560 (0x2170)
MSI: Action 1:44:21: Rollback. Rolling back action:    ccmsetup    2015-08-02 01:44:21    8560 (0x2170)


In the two above scenarios we will end-up with a completely unhealthy ConfigMgr client.

This is why a suggestion has been created to combine the PATCH property method of installing patches and the ClientPatch-concept into a single new way forward of deploying patches.

Go vote!

Acrobat XI and App-V 5 – 2015

I previously wrote a few blog-articles for Acrobat XI and App-V 5, however I thought I would condense the information into a single-post. So, tag along with how you get Acrobat working with App-V 5.

Adobe Customization Wizard XI

Adobe Customization Wizard is a generic tool to edit one type of Acrobat deployment. I previously used (and recommended) leveraging the Adobe Creative Cloud Packager, however there are limitations with the CCP and the ACW has been updated for every version of adobe (currently at the Document Cloud edition).

CCP offers some basic settings (disable updates) and in the end it will actually wrap the MSI into the Adobe lipstick-on-a-pig-approach of deployment.

Settings to consider for your package;

Installation Options

Caching of installation-files should be disabled. This will increase the package size with roughly about ~500mb when its enabled.



To avoid excessive overhead when starting the Virtual Environment there are two Run-registry keys that are removed from the package


In addition you should remove the ability for the end-user to trigger a repair of Acrobat from the help menu. This registry key should be added to disable the Repair-menu option.

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Adobe\Adobe Acrobat\11.0\Installer]



Cleaning up shortcuts is a good idea and completely removing the shortcuts on the Desktop is a not to unusual enterprise practice.


Online Services and Features

Just disable everything in the Online Services and Features view, however quite a few of the settings here will not be captured within the App-V 5 package. We will touch on this later on…



You can set the installer to not install the Adobe Updater services based on the information in their Enterprise Guide. Set the DISABLE_ARM_SERVICE_INSTALL to 1

Reproducible installation

In the end of this process you should have a generic MSI-package along with a specific MST-file that contains your predefined settings. In addition to the settings there are numerous patches for Acrobat. Gather up all of them and ensure that we create a simple file to install all of them in a single run. This is a sample approach containing all but the two latest patches, that I will save in a simple-to use batch file (crude, but does the job).

msiexec /i "%~dp0vc10rt_x64\vc_red.msi" /qb /norestart

msiexec /i "%~dp0acropro.msi" /qb TRANSFORMS=customer.mst
msiexec /p "%~dp0AcrobatUpd11001.msp" /qb /norestart

msiexec /p "%~dp0AcrobatSecUpd11002.msp" /qb /norestart

msiexec /p "%~dp0AcrobatUpd11003.msp" /qb /norestart

msiexec /p "%~dp0AcrobatUpd11004.msp" /qb /norestart

msiexec /p "%~dp0AcrobatSecUpd11005.msp" /qb /norestart

msiexec /p "%~dp0AcrobatUpd11006.msp" /qb /norestart

msiexec /p "%~dp0AcrobatUpd11007.msp" /qb /norestart

msiexec /p "%~dp0AcrobatSecUpd11008.msp" /qb /norestart

msiexec /p "%~dp0AcrobatUpd11009.msp" /qb /norestart

msiexec /p "%~dp0AcrobatUpd11010.msp" /qb /norestart

The not App-v 5 part of the deployment


As stated previously there are somethings that will not be part of the final package. A sample of those are the settings that will limit some Acrobat UI options. Like Updates for example. The reason for this is how Adobe handles these settings. In their wisdom they have (correctly) placed the settings in the registry under a specific key named Policies. App-V 5 will (per default, can be reconfigured) exclude these keys from the virtual registry. Regardless of what is in the package this exclusion happens at the client; this leaves the option of either remove the exclusion (or pass-through as its named) for the client and thereby all packages or to set the natively, or to set them natively. Personally, this is where we go for Group Policy as this is really a setting that should be enforced, however; any and all means for getting a registry key set on the client is fair game.

Registry keys are as follows on a x64-system;

HKLM\Software\Wow6432Node\Policies\Adobe\Adobe Acrobat\11.0\FeatureLockDown



App-V 5 can’t handle drivers and in terms of the Acrobat package there is a specific need to install a printer (aka Distiller) to enable loads of the functionality provided by Acrobat. The printer can not (easily at least) be extracted from the Acrobat installation package, however it is available in the XI-version from other installation packages such as FrameMaker or RoboHelp. There is a minor tweak necessary that was detailed in the previous blog post. Repeating again; This printer needs to be deployed seperately and outside of the App-V 5 sequence. Any means that you have at your disposal will of course to install the driver. One route is to use Dependencies within ConfigMgr, a different route is to install it as a script during the deployment of the App-V 5 package.

App-v 5 sequencer setup


Acrobat will integrate to quite a few different applications and therefore it is vital that these are installed on your sequencer before you start sequencing to avoid issues in the long run. If you want Acrobat to integrate with an application the recommendation is to install it as a prerequisite on the sequencer. Here is the sample used:

Windows 7 x64
.NET Framework 4.0
App-V 5.0 SP2 sequencer
Microsoft Office 2007 SP2
Adobe Illustrator CC 2014
Adobe Photoshop CC 2014


There are exclusions that are required for the application to work, and others will just make your life easier. Here is the list as written previously


REGISTRY\USER\[{AppVCurrentUserSID}]\Software \Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem

REGISTRY\USER\[{AppVCurrentUserSID}]\Software \Microsoft\Windows NT


Decision time

To PVAD or not to PVAD, that is the question. PVAD stands for Primary Virtual Application Directory. If you have been tagging along since the Softgrid days this is the new way to have the Q-drive discussion. Multiple rants have been given about its usage and I have aggressively proclaimed that you should avoid it. However, if you want to use it you should know why you want to use it. And here comes the why in this instance. (as you notice there is an avoidance of explaining what it actually does. Noone is certain, it does give the user full-write access to that directory. Unless you enable VFS-write. Therefore its existance is something you will discover while sequencing with older versions of the sequencer – not in App-V 5.0 SP3 which is the latest – as the option is removed by default in the latest version.

Adobe has an appaling installation method and Acrobat is actually the better part of this, however their licensing engine is far worse. In part of getting their licensing engine operating without issues the user (regardless of their permisson level on the system in question) must posses write-abilities to quite a few directories. App-V 5 RTM and quite a bit along the way was a bit step backwards in terms of functionality when it comes to permissions, and not until the App-V 5.0 SP3 release was there a suistainable way forward. So,

Pre App-V 5.0 SP3

C:\Program Files (x86)\Common Files\Adobe\Adobe PCD

In addition, the directory has to natively exist and the end-user must have full permissions to it.

Post App-V 5.0 SP3
Enable full Virtual Filesystem Write.

The first scenario is extensively tested. The second scenario is a logical continuation, however not extensively tested.


Run your bat-file

Start Acrobat once, and then close it. Stop sequencing. Enable all COM and named objects interaction. Save your sequence.


To enable full-functionality you are required to publish the package globally. A global publication will enable full usage of the Internet Explorer plugin. In addition you are required to enable the integration between any applications that have Acrobat plugins – such as Office applications.

Sample way for Outlook;

reg add HKLM\Software\Microsoft\AppV\Client\RunVirtual\outlook.exe /ve /d XXX_YY /f

Easiest way? Do this when the publishing action occurs. Create the below sample code for the Deployment Configuration-file. Of course, to create beautiful XML like the part below use the Virtual Engine ACE.

 <ProductSourceURLOptOut Enabled="true" />
 <Registry />
 <Arguments>/c reg add HKLM\Software\Microsoft\AppV\Client\RunVirtual\winword.exe /ve /d XX_YY /f | reg add HKLM\Software\Microsoft\AppV\Client\RunVirtual\excel.exe /ve /d XX_YY /f | reg add HKLM\Software\Microsoft\AppV\Client\RunVirtual\powerpnt.exe /ve /d XX_YY /f | reg add HKLM\Software\Microsoft\AppV\Client\RunVirtual\outlook.exe /ve /d XX_YY /f</Arguments>
 <Wait RollbackOnError="false" />
 <Arguments>/c reg delete HKLM\Software\Microsoft\AppV\Client\RunVirtual\outlook.exe /f | reg delete HKLM\Software\Microsoft\AppV\Client\RunVirtual\winword.exe /f | reg delete HKLM\Software\Microsoft\AppV\Client\RunVirtual\excel.exe /f | reg delete HKLM\Software\Microsoft\AppV\Client\RunVirtual\powerpnt.exe /f</Arguments>
 <Wait RollbackOnError="false" />


Known issues

Using the Review Tracker fails to start. Within the installer this is known as the Synchronizer. Not entirely sure why this happens and haven’t spent (any) a lot of time troubleshooting it.

(this can be found under View –> Tracker)

App-V 5 Upgrade without a reboot

This will most likely need to be tested for each version of a potential App-V 5 upgrade, but lets clarify the issue.

When upgrading the App-V 5 client there are some nasty behaviors that cause grievances among users. Since App-V 5 is a middle-man there has been a personal hope that Microsoft would have addressed the upgrade path to offer the same seamless and smooth experience that for example Internet Explorer, Office or general Windows components offers.

What’s the issue?

  1. Install App-V 5 version old
  2. Deploy a few applications, get the users productive
  3. Version new of App-V 5 is released and of course this resolves some issues you have experience
  4. Manually or in an automated manner install the new version.
    All running virtual applications will be terminated
    After the upgrade and until a restart has been completed no applications can be started

The impact of running the App-V 5 version new upgrade when a user is active on the machine is rather frightening and will decrease the end-user productive. Terminating active virtual applications would stop most change management, but halting productivity until reboot is of course a deal-breaker.

Depending on your scenario this issue might be able to workaround using processes alone, however in a road-warrior type of world where devices are laptops and either used or offline the ability to control the upgrade path to minimize user friction is of course limited.

Since two thirds of the worlds corporate Windows client estate is managed by Configuration Manager there are certain abilities to ensure that something is not installed while a user is active on the client. The client would become aware of what needs to be executed, download the necessary files and then would not start the install until the following condition is met;


The issue that still stands is that even though the new App-V 5 version new is installed the user can not start the virtual applications (or interact with the client) until the device is restarted.

It seems that the to avoid a scenario where the device drivers loaded into the system should not be different than service running the upgrade will stop the service. There may of course be issues, however running with this version difference (services vs drivers) within a limited time frame is a minor risk if it allows a gradual upgrade path and setting a new standard baseline for the App-V 5 client version

So, run the App-V 5 upgrade;

Start the App-V 5 client service:
net start appvclient

The user can then restart in their own time and hopefully everything should be running without any major hiccups. Obviously – this is very unsupported.

App-V 5 Deployment Type fails to import in ConfigMgr

After creating a package of Adobe Captivate 8, validating that it runs as expected in a standalone scenario an obstacle come up during the import-process into the App-V 5 Deployment Type into System Center Configuration Manager 2012 R2.

Error code was very generic and only gave a vague reference:

Object reference not set to an instance of an object

Reviewing the activity with Process Monitor only revealed that the before the error occurred the appv-file was parsed. Obviously something within the file itself was generating this error.

No other application spawned the same behaviour, and regardless of how the application was sequenced (different OS, sequencer PVAD etc) the same error always came back.

Based on Microsoft supports reply it seemed to boil down to the fact that a package contained two files with the same name: AppXManifest.xml

The installer for Adobe Captivate generated this file in the following folder;
ProgramfilesCommonX64\Adobe\Adobe Captivate 8 app Packager\assets\AppxManifest.xml

The second file is the generic AppXManifest.xml generated by the sequencer that contains information of howto publish the application.

Workaround: Delete the second instance (not App-V 5 sequencer generated) copy of the file.


Working on some minor details to retrieve loads of data (we will see where this stuff ends-up..)

Here comes a minor Powershell function to retrieve all VMDK filenames from a VMware VMX-file. Output is the VMX-filename (so you know where it came from), the VMDK-files and the location of the VMX-file (so you know where to look for it).

Sample usage:

Get-VMDKFileNamefromVMX -VMX C:\VMs\VM1\vm1.vmx,c:\VMS\VM2\vm2.vmx

A sample output object;

function Get-VMDKFileNamefromVMX {
 Parses a VMX-file for all VMDK-filenames
 Outputs an object with all VMDK-filenames
 Get-VMDKFileNamefromVMX -VMX C:\VMs\VM1\vm1.vmx,c:\VMS\VM2\vm2.vmx
 [Parameter(Mandatory=$False, ValueFromPipeline=$true,
 HelpMessage="Location of VMX-File")]
 $vmx = $vmx.split(",")
 write-verbose "------------------------"
 write-verbose "Start of Get-VMDKFileNamefromVMX"
 Write-Verbose "VMX-files: $($vmx.count)"

foreach ($file in $vmx)
 write-verbose "Search for VMDK in $($file)"
 $vmdkfiles = Select-String -Path $($file) -Pattern vmdk
 write-error "Failed to retrieve $($file)"
 write-verbose "Parsing results for VMDK"
 write-verbose "Found $($vmdkfiles.count) matches of VMDK"
 foreach ($vmdk in $vmdkfiles)

write-verbose "Found: $($vmdk.line)"
 $vmdkfilename = ($vmdk.line).split("=")[1]
 $vmdkfilename = $vmdkfilename.Replace("`"","")
 $vmdkfilename = $vmdkfilename.trim()
 $object = New-Object –TypeName PSObject
 $object | Add-Member –MemberType NoteProperty –Name VMX –Value $($file)
 $object | Add-Member –MemberType NoteProperty –Name VMDK –Value $($vmdkfilename)
 $object | Add-Member –MemberType NoteProperty –Name Location –Value $(Split-Path $file)

 write-verbose "End of Get-VMDKFileNamefromVMX"
 write-verbose "------------------------"


A need arose to determine the latency to a few different nodes and act on that matter. Someone on the internet had almost already written all the Powershell code I wanted. However the code was primarily focused on outputting the results in a CSV-file and not actually using the result in the code afterwards.

Therefore I have re-written this function to output an object instead.


## Based on Ping-Latency
## Rewritten by Nicke Källén
## nicke dot kallen at applepie dot se
## Original header:
## Version: 1
## Tested this script on
##  1) Powershell v3
##  2) Windows 7
function Test-Latency {
 Uses Test-Connection and determines latency to a host
 Outputs each node with Hostname, IP-Address, Latency (ms) and Date
 Test-Latency -ComputerNames,

 [Parameter(Mandatory=$False, ValueFromPipeline=$true,
 HelpMessage="Hostnames or IP-Address seperated by commas")]
 [string[]]$ComputerNames = $env:COMPUTERNAME
 Begin {}

 $ComputerNames = $ComputerNames.split(",")
 foreach ($Computer in $ComputerNames)
 $Response = Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue
 if ($Response -eq $null)
 $object = New-Object –TypeName PSObject
 $object | Add-Member –MemberType NoteProperty –Name Hostname –Value $Computer
 $object | Add-Member –MemberType NoteProperty –Name IPaddress –Value "Unreachable"
 $object | Add-Member –MemberType NoteProperty –Name Latency –Value "No response"
 $object | Add-Member –MemberType NoteProperty –Name Date –Value $(Get-Date)
 $object = New-Object –TypeName PSObject
 $object | Add-Member –MemberType NoteProperty –Name Hostname –Value $($Computer)
 $object | Add-Member –MemberType NoteProperty –Name IPAddress –Value $($Response.IPV4Address)
 $object | Add-Member –MemberType NoteProperty –Name Latency –Value $($Response.ResponseTime)
 $object | Add-Member –MemberType NoteProperty –Name Date –Value $(Get-Date)

 End {}

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\'. Error 0x80070008.  File may be corrupt.

In the end it seemed that the inkling that the 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;



A client without the issues gave us these properties:



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.

Uninstall Software

Based on the previous post handling the removal of the Ask software (the beloved add-on that everyone joyfully installs along with Java) a more developed script took form to handle any type of software.

Its based on the following borrowed pieces of code,

Get-LHSInstInstalledApp has been extended to also output the installationdate. Apart from that everything is as is from the original function

Convert-DateString has been used to convert the InstallationDate string to a date that can be used for calculations

ExitWithCode is a function that is simply used to end the script with an accumulated Exit Code from all uninstallations.

The script will accept the following parameters;

ApplicationName – a wild card search for the applications we want to remove.

PublisherName – we can validate that the right publisher have installed the application

InstallDateOlder – amount of days since the application was installed for us to remove it. Standard is 30

IgnoreInstallDate – True / False – we can choose to completely ignore when the application was installed

If the application is something other than an MSI – it will just report that a productcode is missing and not attempt the installation.

A log-file will be created in %WINDIR%\TEMP\APP_(yourappname)_Removal.LOG

Each uninstall will have a log-file written in %WINDIR%\TEMP with AP_UNINSTALL as prefix.



Running the script requires admin permissions


# Created with: PowerShell ISE
# Created on: 2015-02-21 23:32
# Created by: Nicke Källén
# Organization:
# Filename: SCCM_Uninstall_Unused_Application
# Comment: Uninstalls an application (msi support only) based
# on Display Name in ARP, Publisher and how long ago
# it was installed
# Convert-DateString function
# converting-a-string-to-a-system-datetime-object/
# Get-LHSInstalledApp - appended InstallDate to output
# Get-Installed-Application-615fa73a
# Exit function
# code-from-a-powershell-script
param (
 [string]$ApplicationName = "",
 [string]$PublisherName = "",
 [int]$InstallDateOlder = "30",

 function Convert-DateString ([String]$Date, [String[]]$Format)
 $result = New-Object DateTime

 $convertible = [DateTime]::TryParseExact(

 if ($convertible) { $result }

Function Get-LHSInstalledApp {
 List installed applications for local or remote computers.

 List installed applications for local or remote computers.

 List both 32-bit and 64-bit applications. Note that
 dotNet 4.0 Support for Powershell 2.0 needed.

 Output looks like this:
 ComputerName : N104100
 AppID : {90120000-001A-0407-0000-0000000FF1CE}
 AppName : Microsoft Office Outlook MUI (German) 2007
 Publisher : Microsoft Corporation
 Version : 12.0.6612.1000
 Architecture : 32bit
 UninstallString : MsiExec.exe /X{90120000-001A-0407-0000-0000000FF1CE} 

.PARAMETER ComputerName
 Outputs applications for the named computer(s).
 If you omit this parameter, the local computer is assumed.

 Outputs applications with the specified application ID.
 An application's appID is equivalent to its subkey name underneath the Uninstall registry key.
 For Windows Installer-based applications, this is the application's product code GUID
 (e.g. {3248F0A8-6813-11D6-A77B-00B0D0160060}). Wildcards are permitted.

 Outputs applications with the specified application name.
 The AppName is the application's name as it appears in the
 Add/Remove Programs list. Wildcards are permitted.

.PARAMETER Publisher
 Outputs applications with the specified publisher name.
 Wildcards are permitted

 Outputs applications with the specified version.
 Wildcards are permitted.

 PS C:\> Get-LHSInstalledApp

 This command outputs installed applications on the current computer.

 PS C:\> Get-LHSInstalledApp | Select-Object AppName,Version | Sort-Object AppName

 This command outputs a sorted list of applications on the current computer.

 PS C:\> Get-LHSInstalledApp wks1,wks2 -Publisher "*microsoft*"

 This command outputs all installed Microsoft applications on the named computers.
 * regular expression to match any characters.

 PS C:\> Get-LHSInstalledApp wks1,wks2 -AppName "*Office 97*" 

 This command outputs any Application Name that match "Office 97" on the named computers.
 * regular expression to match any characters.

 PS C:\> Get-Content ComputerList.txt | Get-LHSInstalledApp -AppID "{1A97CF67-FEBB-436E-BD64-431FFEF72EB8}" | Select-Object ComputerName

 This command outputs the computer names named in ComputerList.txt that have the specified application installed.

 Get-LHSInstalledApp | Where-Object {-not ( $_.AppID -like "KB*") } |
 ConvertTo-CSV -Delimiter ';' -NoTypeInformation | Out-File -FilePath C:\temp\AppsInfo.csv
 Invoke-Item C:\temp\AppsInfo.csv

 Outputs all installed application except KB fixes to an CSV file and opens in Excel

 System.String, you can pipe ComputerNames to this Function

 PSObjects containing the following properties:

 ComputerName - computer where the application is installed
 AppID - the application's AppID
 AppName - the application's name
 Publisher - the application's publisher
 Version - the application's version
 Architecture - the application's architecture (32-bit or 64-bit)
 UninstallString - the application uninstall String

 More Info:
 Why not using Get-WmiObject
 * Win32_Product
 At first glance, Win32_Product would appear to be one of those best solutions.
 The Win32_product class is not query optimized.
 Queries such as “select * from Win32_Product where (name like 'Sniffer%')”
 require WMI to use the MSI provider to enumerate all of the installed
 products and then parse the full list sequentially to handle the “where” clause:,

 * This process initiates a consistency check of packages installed,
 and then verifying and repairing the installations.
 * If you have an application that makes use of the Win32_Product class,
 you should contact the vendor to get an updated version that does not use this class.

 On Windows Server 2003, Windows Vista, and newer operating systems, querying Win32_Product
 will trigger Windows Installer to perform a consistency check to verify the health of the
 application. This consistency check could cause a repair installation to occur. You can
 confirm this by checking the Windows Application Event log. You will see the following
 events each time the class is queried and for each product installed:

 Event ID: 1035
 Description: Windows Installer reconfigured the product. Product Name: <ProductName>.
 Product Version: <VersionNumber>. Product Language: <languageID>.
 Reconfiguration success or error status: 0.

 Event ID: 7035/7036
 Description: The Windows Installer service entered the running state.

 I would not recommend querying Win32_Product in your production environment unless you are in a maintenance window.

 * Win32Reg_AddRemovePrograms
 Win32Reg_AddRemovePrograms is not a standard Windows class.
 This WMI class is only loaded during the installation of an SMS/SCCM client.

 What is great about Win32Reg_AddRemovePrograms is that it contains similar properties and
 returns results noticeably quicker than Win32_Product.

 Using Registry:
 By default, if your process is running as a 32 bit process you will end up accessing the 32 bit "reflection" of
 the remote system. Therefore, registry keys like HKLM\Software will actually be mapped to HKLM\Software\Wow6432Node
 which gets very frustrating! You can access the 64 bit "reflection" via WMI, but personally I find that quite painful.

 Fortunately, in .NET 4, the registry class had some extra features added to it which allowed for a new
 overload "RegistryView". Therefore, you can now specify exactly which "reflection" of the registry
 you want to access and manipulate! No more headaches!

 In order to use this function, the Powershell instance must support .Net 4.0 or greater, which is fairly straightforward if you follow these instructions.
 1. Open notepad and copy the below text exactly as shown into the document.

<?xml version="1.0"?>
<configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0.30319"/> <supportedRuntime version="v2.0.50727"/> </startup>

 2. Save this document as c:\windows\System32\WindowsPowerhsell\v1.0\Powershell.exe.config
 (and/or c:\windows\System32\WindowsPowerhsell\v1.0\Powershell_ise.exe.config)
 (in addition for the 32bit Powershell on a 64bit Windows C:\Windows\SysWOW64\WindowsPowerShell\v1.0\*.config)
 3. Reload powershell and type the following command: $PsVersionTable.clrVersion (It should show Major version 4 if .Net 4 is supported.)

 NAME: Get-LHSInatalledApp.ps1
 AUTHOR: u104018
 LASTEDIT: 02/06/2012 16:01:40
 KEYWORDS: Registry Redirection, Installed software, Registry64, WOW6432Node,Accessing Remote x64 Registry From an x86/x32 OS Computer


#Requires -Version 2.0

[cmdletbinding(DefaultParameterSetName = 'Default', ConfirmImpact = 'low')] 


 [Parameter(ParameterSetName='AppID', Position=0,Mandatory=$False,ValueFromPipeline=$True)]
 [Parameter(ParameterSetName='Default', Position=0,Mandatory=$False,ValueFromPipeline=$True)]
 [string[]] $ComputerName=$ENV:COMPUTERNAME,

 [Parameter(ParameterSetName='AppID', Position=1)]
 [String] $AppID = "*",

 [Parameter(ParameterSetName='Default', Position=1)]
 [String] $AppName = "*",

 [Parameter(ParameterSetName='Default', Position=2)]
 [String] $Publisher = "*",

 [Parameter(ParameterSetName='Default', Position=3)]
 [String] $Version = "*"


 ${CmdletName} = $Pscmdlet.MyInvocation.MyCommand.Name

 If (!($PsVersionTable.clrVersion.Major -ge 4)) {Write-Error "Requires .Net 4.0 support for Powershell 2.0"; Return} 

} # end BEGIN

 #Write-Verbose -Message "${CmdletName}: Starting Process Block"
 ForEach ($Computer in $ComputerName) {
 Write-Verbose "`$Computer contains $Computer"
 IF (Test-Connection -ComputerName $Computer -Count 2 -Quiet) {
 try { 

 Write-Verbose "Get Architechture Type of the system"
 $OSArch = (Get-WMIObject -ComputerName $Computer win32_operatingSystem -ErrorAction Stop).OSArchitecture
 if ($OSArch -like "*64*") {$Architectures = @("32bit","64bit")}
 else {$Architectures = @("32bit")}
 #Create an array to capture program objects.
 $arApplications = @()
 foreach ($Architecture in $Architectures){
 #We have a 64bit machine, get the 32 bit software.
 if ($Architecture -like "*64*"){
 #Define the entry point to the registry.
 $strSubKey = "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
 $SoftArchitecture = "32bit"
 $RegViewEnum = [Microsoft.Win32.RegistryView]::Registry64
 #We have a 32bit machine, use the 32bit registry provider.
 elseif ($Architectures -notcontains "64bit"){
 #Define the entry point to the registry.
 $strSubKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
 $SoftArchitecture = "32bit"
 $RegViewEnum = [Microsoft.Win32.RegistryView]::Registry32
 #We have "64bit" in our array, capture the 64bit software.
 #Define the entry point to the registry.
 $strSubKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
 $SoftArchitecture = "64bit"
 $RegViewEnum = [Microsoft.Win32.RegistryView]::Registry64

 Write-Verbose "Create a remote registry connection to the Computer."
 $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computer, $RegViewEnum)
 $RegKey = $Reg.OpenSubKey($strSubKey)

 Write-Verbose "Get all subkeys that exist in the entry point."
 $RegSubKeys = $RegKey.GetSubKeyNames() 

 Write-Debug "Architecture : $Architecture"
 Write-Debug "SoftArchitecture : $SoftArchitecture"
 Write-Verbose "Enumerate the subkeys."
 foreach ($SubKey in $RegSubKeys)
 Write-Debug "`$SubKey : $SubKey"
 $Program = $Reg.OpenSubKey("$strSubKey\\$SubKey")
 $strDisplayName = $Program.GetValue("DisplayName")
 if ($strDisplayName -eq $NULL) { continue } # skip entry if empty display name

 switch ($PsCmdlet.ParameterSetName)

 "AppID" { if ((split-path $SubKey -leaf) -like $AppID)
 $RegKey = ("HKLM\$strSubKey\$SubKey").replace("\\","\")

 $output = new-object PSObject
 $output | add-member NoteProperty "ComputerName" -value $computer
 $output | add-member NoteProperty "RegKey" -value ($RegKey) # useful when debugging
 $output | add-member NoteProperty "AppID" -value (split-path $SubKey -leaf)
 $output | add-member NoteProperty "AppName" -value $strDisplayName
 $output | add-member NoteProperty "Publisher" -value $Program.GetValue("Publisher")
 $output | add-member NoteProperty "Version" -value $Program.GetValue("DisplayVersion")
 $output | add-member NoteProperty "Architecture" -value $SoftArchitecture
 $output | add-member NoteProperty "UninstallString" -value $Program.GetValue("UninstallString")
 $output | add-member NoteProperty "InstallDate" -value $Program.GetValue("InstallDate")

 } #end if
 } #end "AppID"

 "Default" { If (( $strDisplayName -like $AppName ) -and (
 $Program.GetValue("Publisher") -like $Publisher ) -and (
 $Program.GetValue("DisplayVersion") -like $Version ))
 $RegKey = ("HKLM\$strSubKey\$SubKey").replace("\\","\")

 $output = new-object PSObject
 $output | add-member NoteProperty "ComputerName" -value $computer
 $output | add-member NoteProperty "RegKey" -value ($RegKey) # useful when debugging
 $output | add-member NoteProperty "AppID" -value (split-path $SubKey -leaf)
 $output | add-member NoteProperty "AppName" -value $strDisplayName
 $output | add-member NoteProperty "Publisher" -value $Program.GetValue("Publisher")
 $output | add-member NoteProperty "Version" -value $Program.GetValue("DisplayVersion")
 $output | add-member NoteProperty "Architecture" -value $SoftArchitecture
 $output | add-member NoteProperty "UninstallString" -value $Program.GetValue("UninstallString")
 $output | add-member NoteProperty "InstallDate" -value $Program.GetValue("InstallDate")

 } #end if
 } #end "Default"
 } #end switch

 } # end foreach ($SubKey in $RegSubKeys)
 } # end foreach ($Architecture in $Architectures)
 } Catch {
 write-error $_
 } Else {
 Write-Warning "\\$Computer DO NOT reply to ping"
 } # end IF (Test-Connection -ComputerName $Computer -count 2 -quiet)
 } # end ForEach ($Computer in $computerName)

} # end PROCESS

END { Write-Verbose "Function ${CmdletName} finished." }

} # end Function Get-LHSInatalledApp

function Log
 param (
 Write-Debug 'Logging starting'
 Write-Debug "Filename: $($filename)"
 foreach ($txt in $text)
 Out-File $filename -append -noclobber -inputobject $txt -encoding ASCII
 Write-Verbose $txt


 Write-Debug 'Logging ending'


function ExitWithCode

 Write-Verbose "Ending with $($ExitCode)"

function Remove-InstalledMSI
 param (
 [string]$ProductCode = $null,

 Write-Verbose 'Start of Remove-InstallMSI'
 $exitcode = $null


 foreach ($pc in $ProductCode)
 Write-Verbose "Uninstall ProductCode: $($pc)"
 $AppName = $((Get-LHSInstalledApp -AppID $pc).AppName)
 Write-Verbose "AppName: $($AppName)"
 if ($LogFilePath -ne $null)
 $LogFilePath = "c:\windows\TEMP\AP_UNINSTALL_$($AppName).log"
 Write-verbose "LogFilePath: $LogFilePath"
 $argumentlist = "/x $pc /qn REBOOT=ReallySuppress /lv `"$($LogFilePath)`" "
 $argumentlist += $Property
 Write-Verbose "Argument List: $($argumentlist)" 

 $exitcode = (Start-Process -filepath "msiexec.exe" -ArgumentList $argumentlist -Wait -PassThru).ExitCode
 Write-Verbose "Exit Code: $($exitcode)"

 $output = new-object PSObject
 $output | add-member NoteProperty "ProductCode" -value $pc
 $output | add-member NoteProperty "AppName" -value $AppName
 $output | add-member NoteProperty "ExitCode" -value $($ExitCode)
 $output | add-member NoteProperty "LogFilePath" -value $LogFilePath



 Write-Verbose 'End of Remove-InstallMSI'


$logfile = "$env:windir\temp\AP_$($ApplicationName)_Removal.log"
Write-Verbose "Logfile: $($logfile)"
try { Remove-Item $logfile -force -ErrorAction SilentlyContinue }
catch { Write-Warning $_ }

log $logfile '--------------------------------------------'
log $logfile "$(get-date) - $($ApplicationName) - Removal Started"
log $logfile "$(get-date) - $($ApplicationName) - Searching for $($PublisherName) $($ApplicationName)"
 if ($IgnoreInstallDate -eq $true)
 Write-Verbose "IgnoreInstallDate set $($IgnoreInstallDate)"
 log $logfile "$(get-date) - $($ApplicationName) - Ignoring installation date"
 Write-Verbose "IgnoreInstallDate set $($IgnoreInstallDate)"
 log $logfile "$(get-date) - $($ApplicationName) - Removal only if install date is more than $($InstallDateOlder) days ago"

if ($PublisherName)

 $applist = Get-LHSInstalledApp -AppName "*$($ApplicationName)*" -Publisher "*$($PublisherName)*"
 $applist = Get-LHSInstalledApp -AppName "*$($ApplicationName)*"

log $logfile "$(get-date) - $($ApplicationName) - Found $($applist.Count) $($ApplicationName) installations"
log $logfile '--------------------------------------------'
$applist | foreach { write-verbose "AppName: $($_.AppName)"}
$ReturnValue = 0
Write-Verbose "Current Exit Code: $($ReturnValue)"

$applist | where {$_.appid -notmatch ('\{.+\}') } | foreach { log $logfile "$(get-date) - $($_.AppName) has no productcode" }
$applist = $applist | where {$_.appid -match ('\{.+\}') } 

if ($IgnoreInstallDate -eq $true)
 $uninstall = $applist | Remove-InstalledMSI
 $uninstall = $applist | where-object { ($_.InstallDate -notin ($null,'')) -and`
 ( ((get-date) - (Convert-DateString -Date $($_.InstallDate) -Format 'yyyyMMdd')).days -gt $InstallDateOlder ) }`
 | Remove-InstalledMSI

$uninstall | foreach { log $logfile "$(get-date) - $($_.AppName) - Exit Code: $($_.ExitCode)" ; $returnvalue += $_.exitcode }

log $logfile '--------------------------------------------'
log $logfile "$(get-date) - $($ApplicationName) - Removal Finished"





Computer cleanup – Ask removal

Got a lot of computers with the Ask-suite of toolbars installed? When users are allowed administrative permissions on their end-points this is a likely scenario – here is a minor script bit that will cleanup the computer from the hassle.

First – retrieve the Get-LHSInstInstalledApp function from the Technet Gallery. Its a very well written function to retrieve information about both 32-bit and 64-bit applications installed within a Windows environment – great for retrieving information about the installed applications on a computer.

I am not certain if I wrote the Log function on my own, or if this is just something that I grabbed from a script someone previously wrote. If you did write it – just give a shout!


Running the script requires admin permissions


function Log
Out-File $filename -append -noclobber -inputobject $text -encoding ASCII
$logfile = "$env:windir\temp\SMS_Ask_Removal.log"
Remove-Item $logfile -force
log $logfile "$(get-date) - Ask Removal Started"

$ask = Get-LHSInstalledApp -AppName *Ask* -Publisher "APN, LLC"

log $logfile "$(get-date) - Found $($ask.Count) Ask installations"

Foreach ($install in $ask)
log $logfile "--------------------------------------------"
log $logfile "$(get-date) - $($install.AppName) found"
log $logfile "$(get-date) - $($install.AppName) removal"
$exitcode = (Start-Process -filepath "msiexec.exe" -ArgumentList "/x $($install.appid) /qn REBOOT=ReallySuppress /lv `"c:\windows\TEMP\UNINSTALL_$($install.AppName).log`"" -Wait -PassThru).ExitCode
log $logfile "$(get-date) - $($install.AppName) - return code: $exitcode"
log $logfile "$(get-date) - ERROR: $($install.AppName) removal failed"


log $logfile "--------------------------------------------"
log $logfile "$(get-date) - Ask Removal Finished"