Using PowerShell to automate file adds to Teams and SharePoint online

As a bunch of us are starting to convert their environments over to Online SharePoint and Teams we are having to adjust how we do things. Now that teams uses the SharePoint Online as its back end file structure.

Problem: Need to find a way to add files to a teams site from an automated script.

Doing some research found there was a simple way to do this. The simplest easy way to add files to a Teams site is just to send a email to the teams email address. Odds are you are already emailing the report or task results to yourself or a team, why not just add the teams site Address. How do you find the email address? Well inside your teams site and the channel you want to send it to. Click on the three dots. It will look something like below.

Click on the Get Email address button and you will now have the email. This works if you are just sending email results to Teams. But what if you have an attachment. Well that also works. But its listed as an email attachment.

What if I just want to send a file on its own with no email? This is where things got fun. As teams has no real API that you can really use for stuff like this. (I may be wrong, as I could not find one.) Only way I could figure to do this was to add the files direct to the back end SharePoint site.

This requires you to install the PowerShell Module: SharePointPnPPowerShellOnline. This will give you the functions of where you can connect to SharePoint site and upload files directly to where you want them to go.

If you are trying to drop a single file into a teams file share you can run the command below:

$SharepointURL = ""
$OutPath = "C:\Temp\ToSharepoint\Test.txt"
Connect to Sharepoint
Connect-PnPOnline $SharepointURL -Credentials $(Get-Credential)
Send File to Teams Sharepoint site
Add-PnPFile -Folder "Shared Documents/Reports/FolderName" -Path $OutPath

If you want to add contents of a folder you can just add a loop and copy all files from that folder to the teams site.

$SharepointURL = ""
$OutPath = "C:\Temp\ToSharepoint"
Connect to Sharepoint
Connect-PnPOnline $SharepointURL -Credentials $(Get-Credential)
Send Files to Teams Sharepoint site
$Files = Get-ChildItem "$OutPath"
foreach($File in $Files){
Add-PnPFile -Folder "Shared Documents/Reports/FolderName" -Path $File.FullName

Add one of the two above to any of your reporting scripts and now you will have reports directly dumped into your Teams Site “Files” directory.

Posted in CLI and Powershell, Office 365, Random Crap | Tagged , , ,

Using Windows Credential Manager with Powershell.

I have been having trouble for a while dealing with credentials and how to store them when using them in scripts. I have done the whole just do Get-Credential and enter it every time I run the script but that is a great for one off scripts but not for scheduled tasks. In the past i had just been using the Import-Clixml and importing the creds saved from a txt file. This works well but now you have to deal with actual txt files. I ran across a article somewhere reading on something else and remember someone saying something about saving credentials to the Windows Credential manager. After doing some research and some digging and reading found this Gem of a Powershell module. CredentialManager Module is a easy module to use, and simplistic with only 4 commands.


With these 4 commands you can now save credentials and call credentials from the credential manager. This is a huge win for me. No more having to deal with cred files, trying to remember what account created the txt file and fighting that mess.

Creating Stored Credentials

New-StoredCredential -Comment 'Test_Creds' -Credentials $(Get-Credential) -Target 'TestCreds'
Showing the Credentials in the Credential Manager.

Using the stored Credentials

Get-StoredCredential -Target 'TestCreds'

This will show the below but that does not help you.

If you store this into a variable now you can use this variable for your credentials as you normally would.

$TestCreds = Get-StoredCredential -Target 'TestCreds'

Removing Stored Credentials

Cleaning up old credentials is always great housekeeping.

Remove-StoredCredential -Target 'TestCreds'

Using Strong Passwords

Get-StrongPassword -Length 20 -NumberOfSpecialCharacters 4

The command will get you the a password that is 20 characters long with 4 special characters. This is a quick way to generate a password for the needs.

With this little bit of info has saved me a huge amount of time. I am not claiming that Credential manager the most secure method, but its way better than saving the passwords in clear text in the script. And much more manageable than having to deal with txt files.

Posted in CLI and Powershell, DevOps, Random Crap, Security | Tagged , , ,

Horizon Logon Monitor reporting

Updated 3 December 2019

If you have setup your logon monitor this is great. But its lacking a ton. How do you look at this as a holistic basis? How do you start to look at trends? Well there is noting out of the box for you. You pretty much have to build the solution on your own. Well you are in luck. I had some time on a flight to Dallas to kind of throw something together pretty quick.

What I built was a tool that will query the remote Logon Monitor folder, look through each of the log files and collect the following:

  •         Logon Date
  •         Logon Time Stamp
  •         Session Users
  •         Session FQDN
  •         Logon Total Time
  •         Logon Start Hive
  •         Logon Class Hive
  •         Profile Sync Time
  •         Windows Folder Redirection
  •         Shell Load Time
  •         Total Logon Script
  •         User Policy Apply Time
  •         Machine Policy Apply Time
  •         Group Policy Software Install Time
  •         Free Disk Space Avail

I would pull this from the each of the log files and put in a table view and export to a CSV. Yes this is noting to fancy, but from here you an publish the results to a SQL database instead, create a Web front end to show fancy graphs and if you are lucky you can put it behind Microsoft’s Power BI.

To use this you need to follow my previous post and setup Horizon Log on and configure the Remote Logon Monitor path.


Once you get this setup, you can set this script to run as a scheduled task to collect log data. This script is more setup as a framework and will continue to kind add to it as I have the time.

You can access the script here. Or can just be found on my GitHub site.

If you download this and fill in the remote log path and where and what you want to name the CSV. When you run the script you will get a CSV like below.


I have completed some major updates to this script. I have added the ability to turn on and off features. Also added the ability to clean up old log files so you are not filling up drives.

I have incorporated an email function that will attach the days CSV file with the performance stats, and it will also include a bar graph with the average logon times of the last 14 days organized by day. The chart will look like below. It will highlight the lowest time the color Green and the highest one the color Red. The email will also have a breakdown of the Averages for the day.


Also added the SQL functions so you can export the data to a SQL Data Base. As you run the script it will export the data to a SQL table. In a SQL server you have already stood up.  Inside the Git Repo is the SQL script to create the Table, and also the script to run for De-Duplication of the data, you should not run into duplicate records but for me and testing I ran into a ton.

Now from here the possibilities are pretty limitless. You can build a PowerBI site, you could build your own Webpage graphing the stats, or many other options.


Posted in Blogtober, CLI and Powershell, Horizon, VDI, Virtulization | Tagged , , | 1 Comment

Service Now Time Format

If you have started working with Service Now and the API, and started creating Changes with Times and Dates in them. Have you noticed the times are off in your changes, but the time is right in the script. This was a bit of a head scratches for a few, until it dawned on me to think about timezone. Beings for me every script was 6 hours off. So that made me think to write this.

Service Now Date Formats

Field Full Form Short Form
Year yyyy (4 digits) yy (2 digits, y (2 or 4 digits)
Month MMM (name or abbr.) MM (2 digits, M (1 or 2 digits)
Day of Month dd (2 digits) d (1 or digits)


Service Now Time Formats

Field Full Form Short Form
Hour (1-12) hh (2 digits) h (1 or 2 digits)
Hour (0-23) HH (2 digits) H (1 or 2 digits)
Minute mm 2(digits) m (1 or 2 digits)
Second ss (2 digits) s (1 or 2 digits)

By default service now uses the time format of:

yyyy-MM-dd HH:mm:ss

The time field only accepts String response so you must convert the time to a String.

When using PowerShell to typically you would do something like this:

(Get-Date).ToString(‘yyyy-MM-dd HH:mm:ss’)


But beings we are 6 hours off we have to do a bit outside of the box. For our environment we are set to UTC time for scripting but from my understanding is you can change this.

Here is my work around for this issue.

(Get-Date).AddHours(6).ToString(‘yyyy-MM-dd HH:mm:ss’)

You can use the AddHour to adjust time as needed, or you can use a variable to adjust as needed.  Or the best option is to convert the time to UTC using the method: ToUniversalTime()

(Get-Date).ToUniversalTime().ToString(‘yyyy-MM-dd HH:mm:ss’)

By using this you don’t have to mess with adjusting with daylight savings time BS.


Hope this helps going forward.



Posted in CLI and Powershell, DevOps, Service Now | Tagged , ,

Horizon View Instant Clone Pools Upgrade from 6.5 to 6.7

For those of you still yet to take the plunge from 6.5 to 6.7 there is one huge got you with it. It appears a bunch of people miss a single step. So here is my warning.

If you are using VMware Horizon and using Instant Clones you need to plan your migration accordingly.



  1. Take a snapshot of the parent VM on which you upgrade Horizon Agent to Horizon 7 version 7.5 or later. This snapshot is the master image for instant clones.
  2. Set the Storage Distributed Resource Scheduler (DRS) migration threshold to 3 in the cluster.
  3. Disable the instant-clone desktop pools.
  4. Upgrade vCenter Server to vSphere 6.7.
  5. To put the hosts that you plan to upgrade into maintenance mode, choose one of the following options.
    • Put the host directly into maintenance mode from vSphere Web Client then upgrade the host to vSphere 6.7. After the upgrade completes, use vSphere Web Client to exit maintenance mode.

    • Use the icmaint.cmd utility to mark a host for maintenance with the ON option. Marking a host for maintenance, deletes the master images, which are the parent VMs in vCenter Server from the ESXi host. Put the host into maintenance mode and upgrade to vSphere 6.7 ESXi. After the upgrade completes, exit the host from maintenance mode. Then, use the icmaint.cmd to unmark the host for maintenance with the OFF option.

  6. Enable the instant-clone desktop pools.
  7. Perform a push-image operation for each instant-clone desktop pool that uses the new snapshot.

    Only the hosts that are upgraded to vSphere 6.7 ESXi are used for provisioning. The instant clones created during the push-image operation might be migrated to other hosts that are not yet on vSphere 6.7.

  8. Verify that all hosts in the cluster are upgraded to vSphere 6.7.
  9. If you upgrade the parent VM from a previous version to be compatible with ESXi 6.7 and later (VM version 14), then upgrade VMware Tools on the parent VM. You must take a new snapshot of the parent VM, which is the master image for instant clones and perform a push-image operation on all the instant-clone desktop pools that used the previous version of this master image.
  10. If the Virtual Distributed Switch (vDS) is upgraded, power on the parent VM on to verify that there are no network issues. Following a vDS upgrade, you must take a new snapshot of the parent VM and perform a push-image operation on all the instant-clone desktop pools.

Pulled from the VMware Doc


If you decide not to do this you will get something like this……

WARN  (1008-1878) <CacheRefreshThread-https://vcenter:443/sdk> [ObjectStore] Host: HostName not available for provisioning. ConnectionState=connected, PowerState=poweredOn, MaintenanceMode=false, AdminRequestedMaintenance=0, MarkedAsFailed=false, Host Version: 6.5.0, Host API Version: 6.5

Just a the little got ya’s………

Posted in Horizon, vCenter, VDI, Virtulization | Tagged , , ,

Public Speaking around the world.

Last year I set out to put a huge effort to refine my public speaking skills and try to step it up a notch. I have reached out too many in the community to give feedback and just general over all content feedback. And I have received some amazing feedback.  With this feedback I have been able to put this into my presentations and adapt my Snarky and Humorous presentation style. Because of this I have been given an amazing opportunity to spread the word of VDI and automation around the world.

I have been able to present at places like:

Indianapolis, Sydney, Melbourne, St Louis, VMworld in San Francisco, Dallas, Rochester, Phoenix and  Portland to name most.maxresdefault

Through the year I have been able to do continues improvements and adapt more and more as I am adopting new things and adding new content. And looking back my presentation was focused on Automation in VDI with the purpose of showing my Recompose and Refresh Automation script. But as I have had time, and I have grown my skills as a presenter and my skills as a script writer, I have been able to create huge new content, take on monstrous projects like the AsBuilt Report for Horizon, and add these things to my presentation. Making it more of a jam packed presentation that is extremely hard to fit in to the 40 min time slot. By doing this I have able to grow new friendships with some amazing people because of my travels and the things I have done in the community. I cannot thank enough people for where I have made it. And if I start naming people, I will just leave someone out.

Also, by doing this it has really made me realize a bunch of things along the way. How important the community is, how we all help each other out at a blink of an eye. How important it is to have a great place to work, and willing to allow you to go out and speak and give back to the community. Also, how important it is to build a work and life balance. With out that you will go insane, I think. Construction

So, to everyone thank you for allowing me to come out and speak and do my part in helping the community And allowing me to meet some amazing people along the way. Not to mention a huge thank you to all the community for you support and allowing me to grow as a speaker, scripter, and community advocate.

Here is to an amazing 2019 with a few months to go, and Hopefully I will be able to make the rounds to some more amazing places next year and visit more countries, and bring the user back to the UserCon.


Posted in Blogtober, Public Speaking, Random Crap, VDI, VMworld | Tagged , , , ,

Random AppVolumes Fixes for common issues.

I know there are a bunch of people that have tried to get apps to work in AppVolumes and have given up. And some that are almost impossible. These are just little things that I have found to help with some of this stuff.

1.  Issues with printers in App Stacks not showing up. Well after some digging I managed to find the solution.

  • Mount the AppStack for update
  • Once in Provisioning mode Open Uninstall the printer and or Application.
  • Go to C:\snapshotvolumetemp\snapvol.cfg (This is a hidden vol that shows up with in provisioning mode)
  • Add the following entries:
    • exclude_registry=\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Control\Print\Printers
    • exclude_registry=\REGISTRY\MACHINE\SYSTEM\ControlSet001\Control\Print\Printers
  • Install the application or printer again.
  • Complete Provisioning
  • Test


2. Issues with getting relic applications. I am sure some of you have ran across some really old applications that you can not install in Windows 10. Well I have been using a simple trick from a old application I love so much. Use ThinApp to package legacy applications and then use the MSI installer to install the application into the AppStack or just use the shortcuts. That will allow you to package an application like Avaya Site Administrator that requires you to use DirectPlay that is a deprecated Windows feature. But in Windows 7 its not required. So you can package the application with ThinApp and then push it to the AppStack and use it that way till someone updates there old code.


3. Fighting with stubborn apps like FireFox and AppStacks. When you install the latest version of Firefox into a AppStack you run into issues with crashing while using it, or Just plain will not open.

When you install an application into an AppStack its installed in C:\Program Files but when a AppStack is mounted its mounted with C:\snapvolumestemp\mountpoints\GUID\svroot\programs files

And your Applications are launched from a location of C:\SVRoot\Programs

Very few applications do an Integrity check when they are opened, and just so happens Fire Fox is one of those applications. In order to fight this you can do the following.

  • On master image Open registery
  • Open Regedit and browse to: HKLM\System\CurrentControlSet\services\svdriver\Parameters
  • Edit the Multi-String named “HookInjectionWhitelist”
  • Add *firefox.exe||* to the list. It will complain about white space just accept and go.
  • Close out the session run through your cleanup process and shutdown, take your snapshot and test.

Found a great explanation of what the HookInjection White list is used for in App Volumes here:

“AppVolumes driver uses reparse technique to redirect every file access request to their respective appstack/writable. There are few applications which perform integrity checks on the opened file handle. Integrity check means, an application opens a file and gets the handle and then it queries for the path from the handle and compares them. In case of AppVolumes, both the paths are different and integrity check fails. This is where hook was introduced to fake paths returned to satisfy the Integrity check.

HookInjectionWhitelist is used to make sure that we do not inject ourselves to every process, instead to only inject to processes which perform some sort of integrity check.”

Found this blurb Here


4. Now with that being said about Firefox lets flip the coin a bit, And when you are having issues with Apps like Chrome running inside a AppStack. Some people get crashing Chrome, Some times you get the Sad Face “Untitled” tab when you open Chrome. But you are in luck there is a easy way to fix this. Unlike before where HookInjection White List fixed Firefox with Chrome its been know to break it. So if you are running Chrome in an AppStack try removing *chrome.exe||* from the Registry Value below on your Master Image:


Multi-String named “HookInjectionWhitelist”

Snapshot the Image and test. I ran across this in searching for info about HookInjection and came across this. I have not ran into this issue but it has been pretty widespread. You can read the Thread I found this on Here.


More or less little blurbs for me to remember later on.




Posted in Random Crap, Virtulization, VDI, Blogtober, Horizon, App Volumes | Tagged , , , , , ,

Office 365 and Horizon Clones

For those of you wrapped been cursed or blessed with Office 365 this blog post is for you. There are some huge benefits to go this way, but also some huge challenges with doing this. And there are not many blog posts out there about this thing and how to make it work with Horizon and DEM or UEM (Dynamic Environment Manager ) depending on how you want to think of it.

One of the big changes if going from KMS, to using office activation that checks into the Microsoft portal. For traditional desktops and persistent VM’s this is great! But for Clones this is not such a great solution.

Step 1….. Make sure the office install on the clone is in shared licensing mode! Well how do you do this? Well if you are doing a fresh install you can configure your XML to include the line:

<Display Level=”None” AcceptEULA=”True” />

<Property Name=”SharedComputerLicensing” Value=”1″ />

Or if you have already installed Office you can change the Reg Key:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration you can edit the Key “SharedComputerLicensing” to the value of ‘1’

By doing one of the above this will set office on the clone master to Shared mode. Now to validate.

Step 2….. Validating the authentication is in shared license mode by checking to lic files exist. I like to test and make sure things work before throwing things into the wild. So I take a snapshot of the VM set a local account, and sign into the master. Log into office with my credentials and validate the license files exist. By default the files are saved in the location:


There will be two files created in this directory. One .authstring and one .signingcert. These are your Office 365 keys. Now we are sure things are working we can revert to the last snap and proceed on.

Step 3….. Setting up DEM (Dynamic Environment Manager). When setting up DEM you really need to grab a few things. Specific for Office 365 License files you need to capture those. So setting up office shared settings as below:


This needs to be set for minimum. But the if you want to capture the Office Quick links at the top of the office apps you much capture the entire Office folder below.


The quick links are saved here in files labeled OfficeUI like below.


Generally this is my go to configuration for office shared settings in DEM.


Step 4….. Throwing into test environment and making sure things work as expected.

There is some pretty good documents from Microsoft to set this up. Setup and Troubleshooting


Bonus Info….. Microsoft Teams for Horizon

For those of you that have struggled with Teams and getting this to work. Well Microsoft finally built the installer for it to work correctly. Instead of installing in the user directory, now it actually installs in Programs. Huge win, but there are some special things you need to do to make it all work.

Step 1….. Installing Teams. Well this is just not a simple click and go install (Not that I was a huge fan of that method), now you need to download the correct installer.  Installer downloads can be found here 64bit and 32bit

Run the following command to install:

msiexec /i <path_to_msi> /l*v <install_logfile_name> ALLUSER=1

One catch to install this version of teams. It requires Horizon Agent to be installed. Also there are a bunch of Known issues and Limitations. Technically deployment for Non-Persistent is not supported but have found it works quite well. Link to Limitations here.

After install it will work for Clone VM’s. There is really just one catch to the mix. A Shortcut does not exist. So for me I just use DEM and build a shortcut to put in the start menu.


Microsoft has has a decent doc for setting up Teams for Virtual Desktop Use here.


Also one last thing…..

If you are splitting out your Standard build of Office 365 on the base image, and then installing Visio and or Project on AppStacks you need to make sure that the full install of Office 365 is installed on the packaging machine and set to Shared Licensing also like your clone master. The license settings must match.



Posted in Blogtober, Horizon, Office 365, VDI, Virtulization | Tagged , , , , ,

Horizon Logon Monitor

A hidden Gem integrated as part of the VMware Horizon Suite is the Logon Monitor. This Freelittle addition is an optional install as part of your horizon agent and requires a bit of setup to make this work. But if you are wanting a way to monitor your log on process and get some insight into whats going on, well here is the FREE tool to do so.

Setting up the Horizon Logon Monitor is a pretty simple process. Its installed by default with the Horizon agent since 7.2. The little know thing is you have to enable it. Enabling the Logon Monitor is done so on each of the connection servers. On each of the connection servers open a command prompt and enter the following:

vdmadmin -I -timingProfiler -enable

That will enable the Logon Monitor and if you want to disable just change -enable to -disable.

The next part is you will need to enable the Logon Monitor service on the Master Images. Just change the services from disabled to automatic.

Logon Service

So that is a pretty simple install process. So from this default setup it will save your Logon Monitor logs in the default location of:

C:\ProgramData\VMware\VMware Logon Monitor\Logs

What does this actual capture? Well it captures a wealth of information from your VM.

FQDN, IP, DNS Servers, User Name from the VM its self. Also as it logs on it starts pulling data from things like:

Logon Time, Logon Start to Hive Loaded, Logon Start to Classes Hive Loaded, Profile Sync Time, Windows Folder Redirection Apply Time, Total Logon Script Time, Users Policy Apply Time, Machine Policy Apply Time, Group Policy Software Install Time, and Free Disk space.

A full list of metrics that can be captured please look at the VMware Website located here. This is an excerpt of that.

Logon Monitor Metrics
Metric Parameters Description
Logon time
  • Start
  • End
  • Total Time
Metrics include the time logon starts on the guest, logon is completed and the profile is loaded and the desktop is visible, and the total time spent processing logon on the guest. Excludes any time spent outside of the guest.
Session start to logon start time Total time Time from when Windows created a user session until logon began.
Profile sync time Total time Time Windows spent reconciling user profile during logon.
Shell load
  • Start
  • End
  • Total Time
Windows provides the start time of the user shell load. The end time is when the explorer window is created.
Logon to hive load time Total time Metrics provide total time from when the logon starts to when the user registry hive is loaded.
Windows folder redirection
  • Start
  • End
  • Total Time
Metrics related to the time Windows folder redirection starts and is fully applied, as well as the total time to enable Windows folder redirection. This time can be high for the first time folder redirection has been applied or if new files are being uploaded to the redirected share.
Group policy time
  • User group policy apply time
  • Machine group policy apply time
Metrics related to applying group policy to the guest include the time it took to apply user group policy and machine group policy.
Profile metrics
  • Profile type: local, roaming, temporary
  • Profile size: number of files, number of folders, total megabytes
Metrics related to the user profile indicate the type of user profile and whether it is stored on the local machine, on a central profile store, or deleted after logoff.

The profile size includes metrics on the number of files, the total number of folders, and the total size in MB of the user profile.

Profile size distribution
  • Number of Files Between 0 and 1MB
  • Number of Files Between 1MB and 10MB
  • Number of Files Between 10MB and 100MB
  • Number of Files Between 100MB and 1GB
  • Number of Files Between 1GB and 10GB
A count of the number of files in various size ranges in the user profile.
Processes started during logon
  • Name
  • Process ID
  • Parent process ID
  • Session ID
These values are logged for each process that starts from the time the session starts until the logon is complete.
Group policy logon script time Total time Metrics related to executing group policy logon scripts report total time spent executing group policy logon scripts.
Group policy power shell script time Total time Metrics related to executing group policy power shell scripts indicate time spent executing group policy power shell scripts.
Memory usage
  • Available bytes: min, max, avg
  • Committed bytes: min, max, avg
  • Paged Pool: min, max, avg
WMI metrics related to memory usage during logon. Samplings are takings until logon is complete. Disabled by default.
CPU usage
  • Idle CPU: min, max, avg
  • User CPU: min, max, avg
  • Kernel CPU: min, max, avg
WMI metrics related to CPU usage during logon. Samplings are taken until logon is complete. Disabled by default.
Are logon scripts synchronous? Reports whether group policy logon scripts are executed synchronously or asynchronously to the logon.
Network connection status
  • Dropped
  • Restored
Reports whether the network connection is alive or disconnected.
Group Policy Software Installation
  • Asynchronous: True/False
  • Error Code
  • Total Time
Metrics related to group policy software installation indicate whether the installations are synchronous or asynchronous to the logon, if the installations succeeded or failed, and the total time spent installing software using group policy.
Disk Usage For Profile Volume
  • Disc space available for user
  • Free disk space
  • Total disk space
Metrics related to the disk usage on the volume where the user profile is stored.
Domain Controller Discovery
  • Error code
  • Total time
Domain controller related metrics. Error code indicates if there is a failure reaching the domain controller.
Estimated network bandwidth Bandwidth Value collected from Event ID 5327.
Network connection details
  • Bandwidth
  • Slow link threshold
  • Slow link detected: True/False
Values collected from Event ID 5314.
Settings that can affect logon time
  • Computer\Administrative Templates\Logon\Always wait for network at computer startup and logon
  • Computer\Administrative Templates\Logon\Run these programs at user logon
  • Computer\Administrative Templates\User Profiles\Wait for roaming user profile
  • Computer\Administrative Templates\User Profiles\Set maximum wait time for network if a user has a roaming profile or remote home directory
  • Computer\Administrative Templates\Group Policy\Configure Logon Script Delay
  • User\Admin Templates\System\Logon\Run these programs at user logon
  • User\Admin Templates\System\User Profiles\Specify network directories to sync at logon, logoff time only
Metrics from Horizon Agent, Persona Management, App Volumes VMware products that interact with Logon Monitor report custom metrics in the Logon Monitor logs. These metrics can help determine if one of these products might be contributing in a negative way to the logon time.

This table was acquired from the above link on VMware Website.

There are some other metrics you can gather as part of this but it does cause a bit of a performance hit on logon, but nothing anyone would notice. You can enable the following:

 CPU and memory metrics and Process Events and Logon Script Metrics

Those settings will require a bit more configuration. There are some configurations and customization you can do with Reg Keys on the master images. There are some Reg Keys you can create to customization to your deployment of this. VMware Link to settings. Here are the Reg keys that can be edited in the following location on each of the master images. The reg keys are located here: HKLM\Software\VMware, Inc.\VMware Logon Monitor

Below are the configuration values found at HERE

Logon Monitor Configuration Values
Registry Key Type Description
RemoteLogPath REG_SZ Path to remote share to upload logs. When logs are copied to remote log share they are placed in folders specified by the RemoteLogPath registry key. Example: \\server\share\%username%.%userdomain%. Logon Monitor creates the folders as needed. Disabled by default.

  • UNC Path to remote log FOLDER
  • Optional; if not configured, log is not uploaded.
  • Optional local environment variables supported.
Flags REG_DWORD This value is a bitmask to influence the behavior of the logon monitor.

  • The value to set or remove to enable or disable CPU and memory metrics is 0x4. Disabled by default.
  • The value to set or remove to enable process events and logon script metrics is 0x8. Disabled by default.
  • The value to set to enable or disable integration with Horizon 7 is 0x2. Enabled by default.
  • The value to set to disable crash dumps is 0x1. Dumps are written to C:\ProgramData\VMware\VMware Logon Monitor\Data. Disabled by default.
  • The value to set to create folders per user in remote path is 0x10. Disabled by default.
LogMaxSizeMB REG_DWORD Maximum size of the main log in MB. Default is 100 MB.
LogKeepDays REG_DWORD Maximum number of days to keep the main log before rolling it. Default is 7 days.

Inside registry it would look something like this.



This is where you can set the Remote Log path, and can set it to create custom named logs for each person, set number of days to keep, and max size.

This is where the product begins to shine. Now you can save all those logs in central location so you can run though them when you need too, rather than being lost when the user logs off.

Logon Monitor logs will look something like:



This will now help you with Logon troubleshooting. More to add to this in the near future!

Posted in Blogtober, Horizon, VDI, Virtulization | Tagged , , ,

Automation of the Recompose/Push Image Process for Clone Pools and update in code. (Consolidation of previous posts)

This is script is the culmination of many hours of work at night and on the weekends. This has been a personal goal of mine was to automate as much of the daily job that I can. And this is where I chose to start. This will allow me to start becoming Proactive instead of reactive.

So here I go! I give you the ability to do full monthly or weekly or daily update cycles of your clone pools from start to finish 100% scripted. Yes, you are reading that right. With this you are able to do software installs based on SCCM and/or Windows updates, Do 3rd Party patching, shut down the VM take a snapshot, clean out the old snaps, power it back on and prepare for the process to start next month all while updating ServiceNow Incident and Change. Yes, that sounds like a ton of stuff and it really is.

Currently, this has been tested and is running on production systems Windows 7, 8, and 10 64bit systems only. I have also tested the code on Horizon 7.4,7.5,7.6, and 7.7. Also tested with vSphere 6.5 and 6.7. Anything outside of this has not been tested.

Time to get down to the features. What is does this script do? Well, here you go:

  1. Captures Service accounts and passwords for Horizon, and Service Now
  2. Connects to the Horizon Connection server and pulls an inventory of pools and get the info for the Master Images, Pool Names, vCenter Info.
  3. Create a ServiceNow Incident and a ServiceNow Change.
  4. Builds out Installation and Shutdown script on a Share location that distributes the scripts to the VDI Master images.
  5. Runs the Install Updates function.
    • Runs a report of what the current installed 3rd party software is before updating
    • Install updates from SCCM
    • Install 3rd Party from SCCM (Requires Automatic install of Software)
    • Install Windows updates (If not managed by SCCM)
    • Install 3rd Party updates (If not managed by SCCM, and requires you to find appropriate scripts to install updates. There are many things on GitHub for the installs.)
    • Run a report of what the current installed 3rd party software is after updating.
    • Run a report of what the installed updates are since the last recompose
    • Create or Update a custom registry key to store the Corporate Build Date.
    • Create or Update a custom registry key to store the Last Recompose Date
    • Create or Update a custom registry key to store the Last Update Date.
    • Create or Update a custom registry key to store the Pool Assignment Type.
    • Create or Update a custom registry key to store the Pool Name.
    • Create or Update a custom registry key to store the Pool Provision Type
    • Create or Update a custom registry key to store the Pool Type.
    • Create or Update a custom registry key to store the Pool Name.
    • Create or Update a custom registry key to store the Windows Build Version.
    • Create or Update a custom registry key to store the Windows Revision.
    • Create or Update a custom registry key to store the Windows Version.
  6. Reboot the master image, it will run the cleanup script. This will clean up the master and make it ready to be cloned.
    • Checks the version of windows. (This is used for the optimizer at the end.)
    • Create or Update a custom registry key to store the current Windows Core Build Version.
    • Runs Disk Cleanup
    • Runs Defragment
    • Pre-Compile .NET Framework
    • Runs SEP update, Scan, Forced Check-in, and Clone Prep.
    • Runs VMware Optimizer based on what OS version you are on and what templates you have defined. (Beings its ran via CMD there are no rolling back changes, it’s a bug/Feature in the Optimizer)
    • Disables or Stops Services like Windows Updates, SCCM, Adaptiva. AppVolumes
    • Clean out Downloads Cache folder
    • Clean out Windows Prefetch
    • Clear the event logs
    • Releases IP Addresses
    • Clears DNS
    • Shutdown the VM
  7. Connect to each of the vCenters.
  8. Create a snapshot of each of the Masters in there Powered Off State.
  9. Update the VM Notes in vCenter to show Last Recomposed date, Last Update Date, Windows Core Build Version, Pool Type. and Pool Name based off the custom Registry Keys
  10. Will remove the old snapshots past X number of days old.
  11. Will Power the Master VMs back on.
  12. Start the SCCM and Windows Update services.
  13. Copy’s the log files from each VDI Master back to the central share.
  14. It will connect to the Horizon View Connection servers.
  15. Recompose or do image Push to the Pools, Varying the times based on if they are Production or Test.SNDev

During this entire process, we will update notes in ServiceNow. All logs generated by this task will be updated in the change.


There a few requirements for this script. 

  • Must be running Powershell v5.1 or newer on the Scripting Server and the Master VMs
  • The account you are running the script as has to have admin rights to the Master Images, Rights to do recompose or image push in Horizon, and appropriate rights in vCenter to take Snapshot, delete snapshots, and write notes.
  • Must have a folder share setup with the Read and Write rights to the user you will run the script as. I set up the following directories like this.
    • \\ShareLocation\VDI_Tools
      • Logs (This is where all the log files will be saved)
      • Scripts (Save this script and run it from this location)
      • CloneTools (Place your VMware Optimizer and templates in that location.
  • The VMware Modules must be installed on the Scripting Server. (Out of laziness I just enable them all.)
  • Must install the HV-Helper module on the Scripting Server. The code can be found HERE.
      1. Click the green Clone or download button and then click Download ZIP.
      2. Extract the zip file and copy the advanced functions Hv.Helper folder to a modules directory.
      3. Check your PowerShell $env: PSModulePath variable to see which directories are in use:
        • User-specific:  %UserProfile%\Documents\WindowsPowerShell\Modules
        • Systemwide: C:\Program Files\WindowsPowerShell\Modules
      4. Unblock the advanced functions to allow them to be executed.
        • In a PowerShell prompt (as Administrator), run the following command, tailoring the path to where you copied the VMware.Hv.Helper folder:
      dir ‘C:\Program Files\WindowsPowerShell\Modules\VMware.HvHelper\’ | Unblock-File
  • This only works on Horizon 7.0.2 and newer deployments! So if you are not there yet get to updating.
  • If you want to run VMware OS Optimization Tool Fling you must have it downloaded and installed in the “CloneTools” share directory, with your custom Templates. For example, this is my configuration. OptimizeFolder
  • All VDI Master Images must be domain joined. It uses domain authentication to do the remote PowerShell.
  • Enable-PSRemoting must be enabled on All the VDI Master Images.

User Editable Variables for Primary portion of Script:

$HVServers = @("","")   #Enter HVServer FQDN in format of ("HVServer1","HVServer2")   
$SnapDays = "-30"   #Number of Days to keep past snapshots on the VDI Master VM must be a negitive number
$RCTimeDelay1 = '1' #Recompose Test Pools Delay in hours
$RCTimeDelay2 = '48'    #Recompose Prod Pools Delay in hours
$TestPoolNC = "Test"    #Test pool naming convention that diffirentiates them from standard pool.

$LogLocation = "C:\VDI_Tools\Logs"  #Location to save the logs on the Master VM. They will be copied to share and deleted upon completion of the script.
$ScriptLocation = "C:\VDI_Tools\Scripts"    #Location for the Scripts on the Master VMs. This folder will remain after script completes
$CloneToolsLocation = "C:\VDI_Tools\CloneTools" #Location on the Master VMs where the VMware Optimizer is stored and any other tools.
$SleepTimeInS = "180"   #Wait timer for windows to install Windows Updates via Windows Update Service. NOT needed if using SCCM to install updates. 
$AdobeUpdate = "AdobeAcrobatUpdate.ps1" #Name of Adobe Update Script. NOT needed if using SCCM to install updates.
$FlashUpdate = "FlashUpdate.ps1"    #Name of Flash Update Script. NOT needed if using SCCM to install updates.
$FirefoxUpdate = "FireFoxUpdate.ps1"    #Name of FireFox Update Script. NOT needed if using SCCM to install updates.
$JavaUpdate = "JavaUpdate.ps1"  #Name of Java Update Script. NOT needed if using SCCM to install updates.
$CustomRegPath = "HKLM:\Software\YourCompanyName\Horizon\" #Custom Registry Path to create a Key that keeps record of when it was last updated.
$RunOptimizer = '1' #To run Optimizer enter 1, If you do not want to run enter 0
$RunSEP = '1' #To run Optimizer enter 1, If you do not want to run enter 0
$RunServiceNow = '1'    #To Create Service Now Incidents and Change Tickets with details of the Recompose process. 
$OptimizerTemplateNamingPrefix = "TemplateName" #Template Naming i.e. "CompanyTemplate10" for windows 10 Please make sure to append the namy by 10 for win10, 8 for win8, and 7 for win7
$VMwareOptimizerName = "VMwareOSOptimizationTool.exe"   #Name Of the VMware Optimizer Tool
$ShareLogLocation = "\\\VDI_Tools\Logs" #Network Share to save the logs. This will be the collection point for all logs as each VDI Master VM will copy there local logs to this location.
$ShareScriptLocation = "\\\VDI_Tools\Scripts"   #Network Share location where all the scripts are stored and distributed from. 
$ShareCloneToolsLocation = "\\\VDI_Tools\CloneTools"    #Network Share Location where all the Clone Tools are stored and distributed from.
$DomainName = "Domain.Name"     #The Domain that the Master Images are Joined to.
$CorporateBuildRegistryKeyPath = "HKLM:\Software\YourCompanyName\OS\"
$CorporateBuildRegistryKeyName = "BuildVersion"

There are 3 variables that you can set to turn off or on portions of the script. By entering a 0 it will skip those portions of the script.

$RunOptimizer = ‘1’ #To run Optimizer enter 1, If you do not want to run enter 0
$RunSEP = ‘1’ #To run Optimizer enter 1, If you do not want to run enter 0
$RunServiceNow = ‘1’ #To Create Service Now Incidents and Change Tickets with details of the Recompose process.


Looking at the Service Now bits its a bit overwhelmed on the number of variables you ServiceNowneed fill out. But from what I have found its what’s needed for my workflow, you are more than welcome to customize it. The function of the Service Now the piece is that it will open an Incident, and open a change with the Incident as the parent. It will fill out all the change notes with the text from the log files, including what updates were installed, what 3rd party updates were installed and the complete logs of the script.

ServiceNow Variables defined are below. This will require some research on your part. You can follow some of my past posts where I defined how to find the Sys_ID of items in ServiceNow. Link to Building Service Now Functions for adding to existing automation scripts.

#ServiceNow Varibles
$SNAddress = ""

#Incident Varribles

$SNINCCallerID = "Caller Sys_ID" #Look up the Caller User you want to uses Sys_ID in your Service Now instance
$SNINCUrgency = "2" #Look this up in your Service Now instance
$SNINCImpact = "3" #Look this up in your Service Now instance
$SNINCPriority = "4" #Look this up in your Service Now instance
$SNINCContactType = "email" #Look this up in your Service Now instance
$SNINCNotify = "2" #Look this up in your Service Now instance
$SNINCWatchlist = "Watch List Sys_ID" #Can do comma seperated users Sys_ID's
$SNINCServiceOffering = "Service Offering" #Look this up in your Service Now instance
$SNINCProductionImpact = "No" #Well I hope its a No.
$SNINCCategory = "Your Catagory" #Look this up in your Service Now instance
$SNINCSubcategory = "Your SubCat" #Look this up in your Service Now instance
$SNINCItem = "request" #Look this up in your Item menu
$SNINCAssignmentGroup = "Assignment Group Sys_ID" #Look up the Assignment group you want to uses Sys_ID in your Service Now instance
$SNINCAssignedTo = "Assigned To Sys_ID" #Look up the Assignment to user you want to uses Sys_ID in your Service Now instance
$SNINCShortDescription = "Weekly Patch Cycle Updates VDI Pools for $SNDate" #Short Descriptiong of the task
$SNINCDescription = "Weekly Patch Cycle Update for VDI pools.
The following VDI Master Images are being updated with the latest Windows updates and 3rd Party software.
Test Pools will be Refreshed at $SNScriptDate
Production Pools will be Refreshed $SNScriptDate2" #Full description of what you are trying to acomplish this is my example.

#Change Varribles

$SNCHGRequestedBy = "Requested By Sys_ID" #Look up the Requested by User you want to uses Sys_ID in your Service Now instance, Normaly the same as the Caller for the INC
$SNCHGCategory = "Change Catagory" #Look this up in your Service Now instance
$SNCHGServiceOffering = "Service Offering" #Look this up in your Service Now instance
$SNCHGReason = "Change Reason" #Look this up in your Service Now instance
$SNCHGClientImpact = "No" #Look this up in your Service Now instance
$SNCHGStartDate = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') #date in string format. Only way it works.
$SNCHGEndDate = (Get-Date).AddHours($RCTimeDelay2+24).ToString('yyyy-MM-dd HH:mm:ss') #24 hour delay of right now (Can Change if needed) Has to be as a string
$SNCHGWatchList = "Watch List Sys_ID" #Can do comma seperated users Sys_ID's
$SNCHGUrgency = "2" #Look this up in your Service Now instance
$SNCHGRisk = "4" #Look this up in your Service Now instance
$SNCHGType = "Standard" #Look this up in your Service Now instance
$SNCHGState = "1" #Look this up in your Service Now instance
$SNCHGAssignmentGroup = "Assignment Group Sys_ID" #Look up the Assignment Group you want to uses Sys_ID in your Service Now instance
$SNCHGAssignedTo = "Assigned To Sys_ID" #Look up the Assigned to User you want to uses Sys_ID in your Service Now instance

# Example of what I am using for my change information.
$SNCHGJustification = "Weekly Sercurity Patching to install latest Windows updates, 3rd Party updates and Symantec antivirus client update"
$SNCHGChangePlan = "Run report on each VDI Master Image for install Software
Install Windows Updates from SCCM
Install 3rd Party software updates from SCCM
Run Report on each VDI Master Image for installed Software after updates have been completed
Update Custom Reg key to reflect the last date updated
Reboot VDI master VM
Run Shutdown Script that will Run Disk Cleanup, Defragment C drive, Pre-Compile .NET Framework, Run SEP Update, Run full system Scan, Force check-in with SEP server, Run VMware Optimization Tool, Clean out DownLoad's Cache Folder, Clear Event Logs, Release IP, Clear DNS, and Shutdown the VM.
Create a vCenter Snapshot of each VDI Master Image.
Update the vCenter VDI Master Image notes to show current recompose date
Remove Old Snapshots from the VDI Master Images
Power Back on the VDI Master Image
Start all the services
Copy Logs from VDI Master Images to remote Share and upload to this Change.
Refresh each of the pools based on Prod and Test timelines."
$SNCHGTestPlan = "All VDI Clone Pools listed as Test Pools will Be updated 1 hour after script completion, and Production pools will be updated 48 hours later."
$SNCHGBackoutPlan = "If there is a error found, will cancel all future Refresh tasks, and revert the ones that have been refreshed already to the previous snapshot."

There are a couple known bugs:

  1. One is running this more than one time a day will create two snapshots with the same name, and then try to recompose or push the image and get confused as there are two snaps of the same name. So it will fail to update the recompose or push part fo the script. Workaround: Remove the previous snaps for the same day before running the script again. I chose not to put this as the core code as it could delete a running snapshot.
  2. Running the script late in the day, like the last hour of the day. When it runs the script its date dependent. If the script is kicked off on the 12-4-2018 there is a possibility that the snaps might be taken on 12-5-2018, and when the recompose or Push happens it will be looking for a snap from 12-4-2018. In turn, erroring out and failing the Recompose or Push for those pools. Workaround: Run the script early in the day or morning to avoid running into the next day. On at minimum, the script takes 17 mins to run Per Pool. Please plan accordingly.Known Issues
  3. If the credentials that you use to run the script have expiring passwords it will error out and fail the script and not prompt for new creds. (Might be an added feature soon.) Workaround: Use service account with a non-expiring password or delete the password file from the Script Server when you reset your password.
  4. Multi-Domain issues. If your scripting server is in a different domain than your VDI master images you will run into some DNS lookup issues. As in the script, we are not using FQDN to connect to the master Images we are just using hostname. Sorry if this causes an issue. Workaround: Can be fixed pretty simple by adding a domain variable and appending most of the $VMline variables in the script. (Can not promise when I get to fixing this one.)


Here are a few of before and after Shots of what is going on.


Manage Snapshots Before

Snapshots Before

Manage SnapshotsAfter

Snapshots After


vCenter Notes:


vCenter Attributes:


Pool Details:


Pool Before


Pool After

Image Push:


Pending Push


Completed Push

Task List:


Custom Registry Entry:


Log files in the Share:


Installed Software Log Example:

Transcript Log

VMware Optimizer Report:

VMware Optimization Report

Transaction Log Report:

Transcript Log


The Code! This has been a long process and a ton of trial and error to get all this to work.

Link to the code on GitHub!

I will continue to update and optimize this code so check back frequently. If There are any issues found please let me know.


What is in the works for this script you ask?

  • Breaking the Script into Modules so allow better plug and play and customizations.
  • If no ServiceNow building out email functions for reporting. (If someone wants to devote the time to a good HTML body let me know. I just planned on plain text.
  • Add Multi-Threading (I have run into some issues trying to deploy in this version and have held until I had more time to test.)

Last but not least. A huge thank you to Michael McDonnell for the help. Your help and guidance and mentorship have been much appreciated. And thanks to the MANY blog posts of other people and the mass amount of TechNet and PowerShell docs I have read of the last few months. And big thanks to Wouter Kursten for creating the HV-Helper, and answering some of my odd questions!

Posted in CLI and Powershell, DevOps, Service Now, VDI, Virtulization | Tagged , , , , , , , , , ,