Tag Archives: vbscript

Modify Thunderbird Settings using Logon Scripts

Last time with VBScript, I swear it…

While working at UVM on their Exchange 2016 deployment, we came across an interesting environmental anomaly (UVM has lots of those). The popular Thunderbird IMAP mail client installer had been customized by someone (*cough*) to handle various local environment quirks in the UW IMAP server deployment. Once of these was the use of a “mailbox path prefix” variable. When UVM migrated to Dovecot IMAP many years back now, this setting became obsolete, but I, er, I mean *someone* never removed this setting from the custom T-bird installer. Surprisingly, it appears that thousands of users in the environment have the IMAP path prefix setting defined. I guess people really loved that custom Thunderbird installer?

Smug satisfaction with the success of *someone’s* mail client installer evaporated quickly when migrating Thunderbird users to Exchange. Why? As it turns out, this setting unexpectedly causes Thunderbird to point to a non-existent mailbox folder, and thus gives the impression that the Exchange migration had resulted in the deletion of all IMAP server folders. Gah!

It took me only an hour or two to figure out how to fix this problem using PowerShell, but I then discovered that it was not really practical to package PowerShell scripts for execution on non-domain-joined computers. Why?

  1. By default, PowerShell does not allow execution of scripts on new non-domain-joined Windows computers. But even if you could work around that problem…
  2. PowerShell will not trust code signatures unless they explicitly were imported into the “Trusted Publishers” branch of the user’s certificate store.

So, PowerShell is not going to be of overly much use to me today, since we want this script to run on-demand in addition to as a logon script. It really would be nice if I could have taken the time to learn C#, C++, Visual Basic, or some other “real” programming language, wouldn’t it? Because now I have to fall back on VBScript again.

Last time… Last time…

The script below will detect the “mail/” IMAP path prefix and delete it if present. It also will set the server polling interval to 10 minutes if set longer than 10 (29 was the default previously, which does not work well with Exchange IMAP). If Thunderbird is running, the user will be prompted to restart their mail client:

Advertisements

Manifests and Digital Signatures for Self Extracting Scripts

It has been quite in this little corner of the blogosphere lately. Must be because I am not doing anything, right?

Wrong. I have run out of time to blog. But today I will make an exception because I need to provide an update to an old post:
https://jgregscorral.wordpress.com/2012/09/13/wifi-profiles-for-windows-8//

As a substitute for becoming a real programmer, I have for years been writing VBScripts and wrapping them up with the Z-Zip Self-Extracting executable. After the release of Windows 8, this model became more difficult. Out-of-box, the 7-Zip self extractor started generating application compatibility troubleshooter pop-ups on clients. Even prior to that, clients would get warnings asking them “do you really want to execute this scary unsigned possibly-from-a-murdering-hacker” when they launched our executables.

The solution for this is, of course, to add an application manifest to the self-extractor, and then to digitally sign the resulting executable. Easy, right?

I actually did this a few years ago for our venerable Wi-Fi profile installation tool. It was not quite easy, and unfortunately I never did get the process fully automated. The roadblock was in automating the addition of a manifest to the application. Microsoft’s tool for this, “mt.exe”, from the Windows SDK, consistently corrupts my executables. Others in the blogosphere have identified the tool “Resource Hacker” to fill this need:
http://angusj.com/resourcehacker/
https://www.autoitscript.com/forum/topic/113657-autoit-and-manifest/

I added this tool to my ugly-old script packaging batch files, and had good success with eliminating the program compatibility dialogs:

..bin\resource_hackerResourceHacker.exe -addoverwrite %fname%.exe, %fname%.exe, %fname%.manifest, 24,1,

I also was able to streamline the signing process with the following batch code:

set fname=fixThunderbirdMailboxPath
set SDKPath="C:Program Files (x86)Windows Kits10binx86"
set TimeStampURL="http://timestamp.verisign.com/scripts/timstamp.dll"
set /P CertPath="Enter the full path to the PKCS12/PFX signing certificate:"
set /P CertPass="Enter the password for certificate file:"
%SDKPath%signtool.exe sign /f "%CertPath%" /p "%CertPass%" /t %TimeStampUrl% /v %fname%.exe

Migrating to the SCCM UDI for OSD, Part 2d: Driver Handling (continued)

Continued from part 2c:
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2c/

All of the code that we have explored so far has been server-side PowerShell. In this next snippet, we will be seeing client-side VBScript that will be executed in the WinPE environment, while the task sequence is running. In part 2b, I present a Task Sequence builder script. That script created a Task Sequence step with the following command embedded:

cscript.exe %DeployRoot%ScriptsZUVMDetectDriverPackage.wsf

This script uses Model data generated by the “ZTIGather” script (part of MDT), and also some CSV information files that we will explore in part 2e of this post. The data is analyzed to determine which SCCM Driver Packages should be used by this client, and this information is specified in the custom TS variable “UVMDriverPackage”.

I have provided some additional logic to workaround annoying device model names like “Latitude E5440 without vPro” (the with/without vPro models use the same drivers.), and “Venue 11 Pro 7138” (which uses the same drivers as the 7130).

When we start deploying Windows 10 OS Images, the script will need some minor updates to handle the Win10 driver pools. I already have logic for this in my production MDT/LTI client-side scripts. I just need to port that code over here.

In part 2E of this post, I will discuss the final bits of server-side code required to generate the CSV information files used by this client-side script…

Next: Drivers – Providing SCCM database info to clients
http://blog.uvm.edu/jgm/2015/03/09/sccm-udi-2e-drivers/


Series Index:

Parting Scripts – Add a new network printer and set it as default

Some time back, I discovered that a Group Policy Preference that we had applied to a VMware View VDI pool was adding an additional 30 seconds of time staring at the blue spinning donut at each VDI desktop logon.  The policy in question was a printer policy.  Colleagues at other Higher Ed institutions confirmed that they had the same problem with GPP printer preferences.  It has been reported that using the “Mandatory Printers” policy is faster, but this policy does not allow you to assign a default printer.

Enter good old VBScript…

The following script will install a defined network printer and set it as default. If the print share does not exist, an error will be returned. 95% of the code in this script was lifted from my own “KillAndExec.vbs” script from last year. There really is only two lines of new code in here. It is good having a code library to draw on, because it would have taken be days to generate this stuff from scratch. VBScript is so obtuse… so why do I keep using it? Hmmmm….

Rethinking Driver Management in MDT 2013

We have been using the Microsoft Deployment Toolkit (MDT) in LTI/Lite Touch mode here at the University for a long time now.  Why, we used it to deploy XP back when MDT still was called the Business Desktop Deployment Solution Accelerator (BDD).  In this time, we have gone though several different driver management methods.  Gone are the nightmare days of dealing with OEMSETUP files, $OEM$ directories, can elaborate “DriverPack” injection scripts for XP (thank goodness).

With the release of Vista, we moved from a PnP free-for-all model of driver detection.  After Windows 8.0, we found we really needed to separate our drivers by operating system.  Thus, we created Win7, Win8, and WinPE driver selection profiles.

But now we are finding that driver sprawl is becoming a major issue again.  On many new systems we run though a seemingly successful deployment, but end up with a non-responsive touch screen, a buggy track pad, and (sometimes) a very unstable system.

Starting this week, we are trying a new hybrid driver management approach.  We will create a driver folder for each computer model sold though our computer depot.  I have developed a custom bit of VBScript to check to see if the hardware being deployed to is a known model.  Driver injection will be restricted to this model if a match is found.  The script contains additional logic to detect support for both Windows 7 and Windows 8 variants, and to select the most current drivers detected.  Unknown models will fall back on the PnP free-for-all detection method.

Here is how it works…

  1. Create a new group in your OS deployment task sequence named “Custom Driver Inject”, or something similar.  Grouping all actions together will allow easier transfer of these custom actions to other Task Sequences:
    DM-DriverInjectGroup
  2. Under this new group, add a new action of type “Set Task Sequence Variable”.  Name the variable “TargetOS”,and set the value to the OS that you are deploying from this task sequence.  You must follow the same naming convention that you use in your Out-of-box driver folder.  I use Win(X), where (X) is the major OS version of the drivers in the folder.  In this example, I have chose “Win8”:
    DM-SetTargetOS
  3. Add an action of type “Run Command Line”.  Name this action “Supported Model Check”.  Under the Command line field, enter “cscript “%SCRIPTROOT%ZUVMCheckModel.wsf”.  (We will import this script into the deployment share later on.)
    DM-RunModelCheckScript
  4. Add a sub-group named “Supported Model Actions”.  Under the “Options” tab, add a condition of type “Task Sequence Variable”.  Use the variable “SupportedModel”, the Condition “equals”, and the Value “YES”.  (The SupportedModel variable gets set by the CheckModel script run in the previous step.):
    DM-ConditionalGroup
  5. Under this new group, add a new action of type “Set Task Sequence Variable”.  Name this task “Set Variable DriverGroup002”.  Under “Task Sequence Variable”, set “DriverGroup002”, and set the value to “Models%TargetOS%%Model%”.  (Note:  You could use “DriverGroup001”, but I already am using this variable to hold a default group of drivers that I want added to all systems.  The value “%TargetOS%%Model%” defines the path to the driver group in the deployment share.  If you use a different folder structure, you will need to modify this path.):
    DM-SetDriverGroup
  6. Create a new task of type “Inject Drivers”.  Name this task “Inject Model-Specific Drivers”.  For the selection profile, select “Nothing”.  Be sure to select “Install all drivers from the selection profile”.  (NOTE: The dialog implies that we will be injecting only divers from a selection profile.  In fact, this step will inject drivers from any paths defined in any present “DriverGroupXXX” Task Sequence variables.)
    DM-InjectModelDrivers
  7. Now, under our original Custom Driver Inject group, add a new task of type “Inject Drivers”.  Choose from the selection profile “All Drivers”, or use a different fallback selection profile that suits the needs of your task sequence.  This time, select “Install only matching drivers from the selection profile”:
    DM-InjectUnsupported1
    Under the “Options” tab, add the condition where the “Task Sequence Variable” named “Supported Model” equals “NO”:
    DM-InjectUnsupported2
    This step will handle injection of matching drivers into hardware models for which we do not have a pre-defined driver group.
  8. Optionally, you now can open the “CustomSettings.ini” file and add the following to your “Default” section:
    DriverGroup001=Peripherals
    (I have a “Peripherals” driver group configured which contains USB Ethernet drivers used in our environment.  These are a necessity when deploying to hardware that does not have an embedded Ethernet port, such as the Dell XPS 12 and XPS 13.  You also could add common peripherals with complicated drivers such as a DisplayLink docking station or a Dell touch screen monitor.)
  9. Add the “ZUVMCheckMedia.wsf” script to the “Scripts” folder of your deployment share.  The code for this script is included below.  I think the script should be fairly easy to adapt for your environment.
  10. Finally, structure your “Out-of-Box Drivers” folder to contain a “Models” folder, and a single folder for each matching hardware model in your environment.  I get most of our driver collections from Dell:
    http://en.community.dell.com/techcenter/enterprise-client/w/wiki/2065.dell-driver-cab-files-for-enterprise-client-os-deployment.aspx
    (NOTE:  Thanks Dell!)
    The real challenge of maintaining this tree is in getting the model names right.  Use “wmic computersystem get model” to discover the model string for any new systems in your environment.  A table of a few current models I have been working with is included below.

Dell Marketing Model Name to WMI Name Translator Table:

  • Dell XPS 12 (first generation) – “XPS 12 9Q23”
  • Dell XPS 12 (second generation) – “XPS 12-9Q33”
  • Dell XPS 13 (first generation) – “Dell System XPS L321X”
  • Dell XPS 13 (second generation) – “Dell System XPS L322X”
  • Dell XPS 14 – “XPS L421Q”
  • Dell Latitude 10 – “Latitude 10 – ST2”
  • VMware Virtual Machine – “VMware Virtual Platform”
  • Microsoft Hyper-V Virtual Machine – “Virtual Machine”

A fun nuance we encountered last week was a Latitude E5430 model that contained “no-vPro” after the model number. Dell does not provide separate driver CABs for vPro/non-vPro models, so I added a regular expression test for Latitudes, and strip any cruft after the model number. There is one more problem down…

The following site contains a list of older model name translations:
http://www.faqshop.com/wp/misc/wmi/list-of-wmic-csproduct-get-name-results
As you can see, most Latitudes and Optiplexes follow sane and predictable model name conventions. I wish the same were true for the XPS.

Finally, I am indebted to the following sources for their generously detailed posts on driver management. Without their help, I doubt I would have been able to make this solution fly:

Jeff Hughes of the Windows Enterprise Support Server Core Team:
http://blogs.technet.com/b/askcore/archive/2013/05/09/how-to-manage-out-of-box-drivers-with-the-use-of-model-specific-driver-groups-in-microsoft-deployment-toolkit-2012-update-1.aspx

Andrew Barnes (aka Scriptimus Prime), whose posts on MDT driver management give the basics DriverGroups and model selection:
http://scriptimus.wordpress.com/2013/02/25/ltizti-deployments-injecting-drivers-during-deployment/
AND, of automating driver import into MDT (written for MDT 2012… some changes required for 2013):
http://scriptimus.wordpress.com/2012/06/08/mdt-2012-creating-a-driverstore-folder-structure/

The incredible Michael Neihaus, who in this post discusses the use of DriverGroups and Selection Profiles:
http://blogs.technet.com/b/mniehaus/archive/2009/09/09/mdt-2010-new-feature-19-improved-driver-management.aspx

And finally Eric Schloss of the “Admin Nexus”, who give me the idea of developing a fallback for systems that do not match a known model. It was this key bit of smarts that gave me the confidence to move forward with a model-specific driver grouping strategy:
http://adminnexus.blogspot.com/2012/08/yet-another-approach-to-driver.html

ZUVMCheckModel.wsf script:

(NOTE: The filename in GitHub is assigned a “.vbs” extension, but you should use “.wsf” when saving it. This was done to allow for useful syntax highlighting of the GitHub Gist.)

Note: Post updated Fall 2015 to correctly handle Windows 10 operating systems.

Prepare SCCM Clients for Cloning

Annoying task… configuring VMware View desktops for use in an environment that utilizes VMware View.  Some say, don’t put the management agent on the View desktop, just rebuild your desktops every time there is a security patch.  I say, even if recomposing your pools is fast and easy, I still do not want to do it with every patch release.

Best practice for preparing a reference computer for deployment in an SCCM environment is to not include the SCCM client.  However, SCCM client installation is SLOW, so I would like to save time and CPU load by including the software.  Documentation how to do this is sketchy.  Also, I really want a script fired off by the QuickPrep process to do the prep work, so that someone does not forget that it needs to be done.

Here is my first pass at the script… again, not too pretty, but functional:

'==========================================================================
'
'  NAME:    sccmClientPrep.vbs
'
'  AUTHOR:  J. Greg Mackinnon
'  DATE:    2013-02-01
'
'  COMMENT: prepares SCCM client for cloning
'           Requires:
'             - Certutil.exe in %systemroot%system32 (included with Win7)
'           Returns:
'           1 - CCMEXEC service stop failure
'           2 - Machine Certificate Store deletion failure
'           4 - SMS Certificate Store deletion failure
'           8 - SMSCFG.INI deltion failure
'==========================================================================
option explicit

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        CONSTANTS
const MACH_STORE = "My"
const SMS_STORE = "SMS"
const SVCNAME = "ccmexec"
const TIMEOUT = "120"

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        OBJECTS
dim oShell
set oShell = CreateObject("WScript.Shell")

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        VARIABLES
dim sSysRoot,sCUPath,sINIPath
dim iRet, iExit

sSysRoot = oShell.ExpandEnvironmentStrings("%SystemRoot%")
sCUPath = sSysRoot & "system32certutil.exe"
sINIPath = sSysRoot & "SMSCFG.INI"
iExit = 0

'=-=-=-=-=-=-=-=-=-=-=-=-=
'   FUNCTIONS AND SUBS
function stopSvc(sSvcName,iTimeout)
' Stops the Windows service with name matching input string "sSvcName".
' Times out in iTimeout seconds.
' Needs routine to verify that sSvcName is a valid service name.
	'Variables:
	dim bDone 
	dim iSecs 
	bDone = False
	iSecs = 0

	'Objects and Collections:
	dim cSvcs
	dim oWMI, oSvc
	Set oWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\.rootcimv2")
	Set cSvcs = oWMI.ExecQuery("Select * from Win32_Service Where Name = '" & sSvcName & "'")

	'Stop the service if it is running, exit if it is not running
	For Each oSvc In cSvcs
		if oSvc.State = "Running" then
			oSvc.StopService
		else
			stopSvc = 0
			exit function
		end if
		exit for 'Only on service in collection
	Next

	'Check on the service until stopped.  Timeout in iTimeout seconds.
	Do while bDone = False
		Set cSvcs = oWMI.ExecQuery("Select * from Win32_Service Where Name = '" & sSvcName & "'")
		bDone = True
		For Each oSvc In cSvcs
			If oSvc.State  "Stopped" Then
				bDone = False
				WScript.Sleep 1000
				Exit For
			End If
		Next
		iSecs = iSecs + 1
		If iSecs >= iTimeout Then
			stopSvc = 1
			exit function
		End If
	Loop 
	stopSvc = 0
end function

function delCert(sStore,sSerial)
'Deletes certificate in certificate store "sStore" with serial number "sSerial"
'Returns: The ExitCode from certutil.exe
'Requires: 
'   - WScript.Shell object named "oShell"
'   - Defined path to "certutil.exe" named "sCUPath"
'   - Presence of certutil.exe on the system
	dim oExec, oStdOut
	dim sLine
	
	'wscript.echo "About to execute: " & sCUPath & " -delstore " & sStore & " " & sSerial
	set oExec = oShell.Exec(sCUPath & " -delstore " & sStore & " " & sSerial)
	Set oStdOut = oExec.StdOut
	Do While oExec.Status = 0
		WScript.Sleep 100
	Loop
	'Uncomment the next four lines to debug certutil:
	'Do Until oStdOut.AtEndOfStream
	'	sLine = oStdOut.ReadLine
	'	wscript.echo sLine
	'Loop
	delCert = oExec.ExitCode
end function

function getCert(sStore)
'Gets the serial numbers of certificates in the machine store specified by "sStore"
'Sends the captured serial numbers to the "delCert" function for deletion.
'Returns: 1 - If cert deletion files, 0 - If no errors are detected.
'Requires: 
'   - WScript.Shell object named "oShell"
'   - Defined path to "certutil.exe" named "sCUPath"
'   - Presence of certutil.exe on the system
	dim oExec, oStdOut
	dim bFail
	dim i, iRet
	dim sLine, sSerial
	
	bFail=False
	Set oExec = oShell.Exec(sCUPath & " -store " & sStore)
	Set oStdOut = oExec.StdOut
	
	Do Until oStdOut.AtEndOfStream
		sLine = oStdOut.ReadLine
		if InStr(1,sLine,"Serial Number",1) then
			i = CInt(InStr(1,sLine,":",1) + 2)
			sSerial = Mid(sLine,i)
			iRet = delCert(sStore,sSerial)
			if (iRet  0) then
				wscript.echo "Certificate deletion failed"
				bFail = True
			end if
		end if
	Loop
	
	if bFail = True then
		getCert = 1
	else
		getCert = 0
	end if
end function

function delFile(sFile)
'Deletes the file specified by "sFile"
'Requires existing Wscript.Shell object named "oShell"
	dim oFSO, oFile
	set oFSO = CreateObject("Scripting.FileSystemObject") 

	'wscript.echo "About to delete file: " & sFile
	if oFSO.FileExists(sFile) then
		'Delete method will force a WSH quit if it fails, so we need to disable exit-on-error:
		Err.Clear
		On Error Resume Next
		
		set oFile = oFSO.GetFile(sFile)
		delFile = oFile.Delete(True)
		if Err.Number = 0 then
			delFile = 0
		else
			delFile = Err.Number
		end if
	else
		'Exit code for the function could be changed here if you are concerned about
		' the requested file to delete not being present on the system.
		'wscript.echo "File " & sFile & " does not exist."
		delFile = 0
	end if
end function

'=-=-=-=-=-=-=-=-=-=-=-=-=
'          MAIN
iRet = stopSvc(SVCNAME, TIMEOUT)
'wscript.echo "Return from stopSvc: " & iRet
if iRet  0 then
	iExit = iExit + 1
end if

iRet = getCert(MACH_STORE)
'wscript.echo "Return from cert deletion for store " & MACH_STORE & ": " & iRet
if iRet  0 then
	iExit = iExit + 2
end if

iRet = getCert(SMS_STORE)
'wscript.echo "Return from cert deletion for store " & SMS_STORE & ": " & iRet
if iRet  0 then
	iExit = iExit + 4
end if

iRet = delFile(sINIPath)
'wscript.echo "Return from file deletion: " & iRet
if iRet  0 then
	iExit = iExit + 8
end if

WScript.Quit iExit

Scriptomatic Access to the Start Menu and Taskbar

As promised in my previous post, here is my current VBScript for configuring the Windows 7 Start Menu and Taskbar. Not beautiful, but certainly functional. My thanks to JuliusPIV and cogumel0 for doing the heavy lifting that made this script possible.

Note that you really will need to set the Group Policy option to turn off the Start Menu program history if you want Start Menu pinning to be at all effective in streamlining the Windows 7 “first time” GUI.

'==========================================================================
'
' NAME: Pin & Unpin items to/from Start Menu & Taskbar
'
' AUTHOR: J. Greg Mackinnon
' DATE  : 2013-01-31
'
' COMMENT: Derived from code by JuliusPIV found here:
'   http://social.technet.microsoft.com/Forums/en/w7itproinstall/thread/73eb1c0a-fc78-4ae7-ba6d-356d9a9a5328
'
'   To add items to Start Menu or Taskbar, add a variable defining the 
'   path to the original link in the variables section, then add that 
'   variable to the "aPinSM", "aPinTB", or "aUnpinTB" arrays.
'
'   Note that not all links (such as filesystem shortcuts) can be pinned.
'
'   Uncomment "debugecho" lines to troubleshoot.
'
'==========================================================================
option explicit

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        CONSTANTS
'=-=-=-=-=-=-=-=-=-=-=-=-=
'List of "Shell Special Folder Constants" used in the script.  See:
'http://msdn.microsoft.com/en-us/library/windows/desktop/bb774096(v=vs.85).aspx
const ssfAPPDATA = &H1a
const ssfCOMMONPROGRAMS = &H17
const ssfPROGRAMFILESx86 = &H30
const ssfPROGRAMS = &H2 
const ssfSYSTEM = &H25
const ssfWINDOWS = &H24

'=-=-=-=-=-=-=-=-=-=-=-=-=
'         OBJECTS
'=-=-=-=-=-=-=-=-=-=-=-=-=
dim fso, oShell, oShortcut
set fso = CreateObject("Scripting.FileSystemObject")
set oShell = CreateObject("Shell.Application") 

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        VARIABLES
'=-=-=-=-=-=-=-=-=-=-=-=-=
dim aPinSM, aPinTB, aUnpinTB
dim bEchoOut, bPinItem
dim sAUP, sUP, sRAD, sPFx86, sSys32, sItem, sScriptHost, sFileName
dim sGC, sMOW, sMOE, sMOPP, sMOON, sMOO, sOC, sFZ, sPT, sProj, sCalc, sSnip, sPDN, sMag, sKey, sWMP

'Configure variables for well known folders:
sRAD = oShell.NameSpace(ssfAPPDATA).Self.Path & ""            'Roaming AppData
sAUP = oShell.NameSpace(ssfCOMMONPROGRAMS).Self.Path & ""     'Start Menu Programs - All Users
sUP = oShell.NameSpace(ssfPROGRAMS).Self.Path & ""            'Start Menu Programs - Current User
'sPFx86 = oShell.NameSpace(ssfPROGRAMFILESx86).Self.Path & "" 'Program Files (x86)
'sSys32 = oShell.NameSpace(ssfSYSTEM).Self.Path & ""          '%WinDir%system32

'List of links to be added to the Start Menu or Taskbar, relative to:
' C:ProgramDataMicrosoftWindowsStart MenuPrograms
sGC = sAUP & "Google ChromeGoogle Chrome.lnk"
sMOW = sAUP & "Microsoft Office 2013Word 2013.lnk"
sMOE = sAUP & "Microsoft Office 2013Excel 2013.lnk"
sMOPP = sAUP & "Microsoft Office 2013PowerPoint 2013.lnk"
sMOON = sAUP & "Microsoft Office 2013Onenote 2013.lnk"
sMOO = sAUP & "Microsoft Office 2013Outlook 2013.lnk"
sOC = sAUP & "Oracle CalendarOracle Calendar.lnk"
sFZ = sAUP & "FileZilla FTP ClientFileZilla.lnk"
sPT = sAUP & "PuTTYPuTTY.lnk"
sProj = sAUP & "Accessoriesdisplayswitch.lnk"
sCalc = sAUP & "AccessoriesCalculator.lnk"
sSnip = sAUP & "AccessoriesSnipping Tool.lnk"
sPDN = sAUP & "Paint.NET.lnk"
sMag = sUP & "AccessoriesAccessibilityMagnify.lnk"
sKey = sUP & "AccessoriesAccessibilityOn-Screen Keyboard.lnk"
sWMP = sRAD & "MicrosoftInternet ExplorerQuick LaunchUser PinnedTaskBarWindows Media Player.lnk"

'Arrays containing links to be added to StartMenu or Taskbar, or to be removed from the Taskbar:
aPinSM = Array(sOC,sFZ,sPT,sPDN,sSnip,sCalc,sProj,sMag,sKey)
aPinTB = Array(sGC,sMOW,sMOE,sMOPP,sMOON)
aUnpinTB = Array(sWMP,sMOW,sMOPP)

'=-=-=-=-=-=-=-=-=-=-=-=-=
'   FUNCTIONS AND SUBS
'=-=-=-=-=-=-=-=-=-=-=-=-=
function PinSM(shortcut)
	dim oFolder, oFolderItem
	dim sFolder, sFile
	dim colVerbs
	dim itemverb
	
	sFolder = fso.GetParentFolderName(shortcut)
	sFile = fso.GetFileName(shortcut)

	'debugecho "Pinning " & sFolder & "" & sFile & " to Start Menu."
	Err.Clear
					
	set oFolder = oShell.NameSpace(sFolder)
	set oFolderItem = oFolder.ParseName(sFile)
	set colVerbs = oFolderItem.Verbs

	for each itemverb in oFolderItem.Verbs
		if Replace(itemverb.name, "&", "") = "Pin to Start Menu" then itemverb.DoIt
	next
end function

function PinTB(shortcut)
	dim sFolder, sFile
	dim oFolder, oFolderItem
	dim colVerbs, itemverb
	
	sFolder = fso.GetParentFolderName(shortcut)
	sFile = fso.GetFileName(shortcut)

	'debugecho "Pinning " & sFolder & "" & sFile & " to Taskbar."
	Err.Clear
					
	set oFolder = oShell.NameSpace(sFolder)
	set oFolderItem = oFolder.ParseName(sFile)
	set colVerbs = oFolderItem.Verbs
	
	for each itemverb in oFolderItem.Verbs
		if Replace(itemverb.name, "&", "") = "Pin to Taskbar" then itemverb.DoIt
	next
end function

function UnpinTB(shortcut)
	dim sFolder, sFile
	dim oFolder, oFolderItem
	dim colVerbs, itemverb
	
	sFolder = fso.GetParentFolderName(shortcut)
	sFile = fso.GetFileName(shortcut)

	'debugecho "Unpinning " & sFolder & "" & sFile & " from Taskbar."
	Err.Clear
					
	set oFolder = oShell.NameSpace(sFolder)
	set oFolderItem = oFolder.ParseName(sFile)
	set colVerbs = oFolderItem.Verbs
	
	for each itemverb in oFolderItem.Verbs
		if Replace(itemverb.name, "&", "") = "Unpin from Taskbar" then itemverb.DoIt
	next
end function

function debugecho(msg)
	if bEchoOut then
		wscript.echo msg
	end if
end function

sub Main
	for each sItem in aUnpinTB
		if not fso.FileExists(sItem) then
			bPinItem = false
			'debugecho "File, " & sItem & ", to unpin does not exist."
			'debugecho "Please check the input and try again."
		else
			UnpinTB(sItem)
		end if
	next
	for each sItem in aPinSM
		if not fso.FileExists(sItem) then
			bPinItem = false
			'debugecho "File, " & sItem & ", to pin does not exist."
			'debugecho "Please check the input and try again."
		else
			PinSM(sItem)
		end if
	next
	for each sItem in aPinTB
		if not fso.FileExists(sItem) then
			bPinItem = false
			'debugecho "File, " & sItem & ", to pin does not exist."
			'debugecho "Please check the input and try again."
		else
			PinTB(sItem)
		end if
	next
end sub

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        MAIN BODY
'=-=-=-=-=-=-=-=-=-=-=-=-=
'Suppress echo if we are in WScript:
sScriptHost = LCase(Wscript.FullName)
if Right(sScriptHost, 11) = "wscript.exe" then
    bEchoOut = false
else
    bEchoOut = true
end if

call Main