Enabling Flash in 2021

Adobe Flash

If, like me, you have a lot of legacy systems which are reliant on Abode Flash for their management UIs (eg vSphere Client) then Flash being killed off is very inconvenient

Luckily there is currently a fix!

This works as of January 12th 2021, who knows if it will be killed off.

This requires the creation of a file in the data directory of your browser

Google Chrome

To enable Flash in Chrome you need to create a file and provide it with some custom config.

Browse to your user’s local appdata directory ( %localappdata% can be used if it’s configured)

Within in browse to \Google\Chrome\User Data\Default\Pepper Data\Shockwave Flash\

Create a new Folder called “System” and within here create a new file called mms.cfg

edit this file in notepad (other text editing programs are available) and enter the following text:

EOLUninstallDisable=1
EnableAllowList=1
AllowListPreview=1
AllowListUrlPattern=https://*.internaldomain.com

Replacing with your internal domain name to enable Flash on all your internal systems.

Microsoft Edge

As above, but this time your directory is

%localappdata%\Microsoft\Edge\User Data\Default\Pepper Data\Shockwave Flash\System\mms.cfg

Windows Internet Explorer

As above, but this time your directory is

%windir%\SysWOW64\Macromed\Flash\mms.cfg

Brave Browser

As above, but this time your directory is

%localappdata%\BraveSoftware\Brave-Browser\User Data\Default\Pepper Data\Shockwave Flash\System\mms.cfg

How to Build a PowerShell Menu GUI for your PowerShell Scripts

Purely for prosperity I have recreated this post from Nathan Kasco so I can find it better in future. Copyright Nathan Kasco. All the words and code is his.

It’s weekend project time again and today you will learn how to build a lightweight system tray context menu where you can quickly and easily launch your most coveted PowerShell scripts. You can see below the end result.

In this article, you’ll learn how to build your own PowerShell menu GUI by breaking the process down step-by-step.

Table of Contents

  • Environment and Knowledge Requirements
  • Show/Hide Console Window
  • Create Menu Options
  • Creating A Launcher Form
  • Show the Launcher Form

Environment and Knowledge Requirements

Before you dive in, please be sure you meet the following minimum requirements:

For this project, the good news is that you won’t really need to rely on Visual StudioPoshGUI, or any other UI development tool as the primary components that this project will rely on the following:

  • NotifyIcon – This will represent our customizable system tray icon for the user to interact with.
  • ContextMenu – Container for when the user right-clicks on the tray icon.
  • MenuItem – Individual objects for each option within the right-click menu.

Open up your favorite PowerShell script editor and let’s get started!

For this project you are going to build three functions: two functions to show/hide the console to provide a cleaner user experience and one to add items to your systray menu. These functions will serve as a foundation for later use to make your life much easier as you will learn a bit later in this article.

Show/Hide Console Window

Unless hidden, when you launch a PowerShell script, the familiar PowerShell console will come up. Since the menu items you’ll create will launch scripts, you should ensure the console doesn’t up. You just want it to execute.

When a script is executed, you can toggle the PowerShell console window showing or not using a little .NET.

First add the Window .NET type into the current session. To do this, you’ll use some C# as you’ll see below. The two methods you need to load into context are GetConsoleWindow and ShowWindow. By loading these DLLs into memory you are exposing certain parts of the API, this allows you to use them in the context of your PowerShell script:

 #Load dlls into context of the current console session
 Add-Type -Name Window -Namespace Console -MemberDefinition '
    [DllImport("Kernel32.dll")]
    public static extern IntPtr GetConsoleWindow();
 
    [DllImport("user32.dll")]
    public static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
 '

Create two functions using the loaded above using the GetConsoleWindow() and ShowWindow() method as shown below.

 function Start-ShowConsole {
    $PSConsole = [Console.Window]::GetConsoleWindow()
    [Console.Window]::ShowWindow($PSConsole, 5)
 }
 
 function Start-HideConsole {
    $PSConsole = [Console.Window]::GetConsoleWindow()
    [Console.Window]::ShowWindow($PSConsole, 0)
 }

With these two functions you now have created a way in which you can show or hide the console window at will.

Note: If you’d like to see output from the scripts executed via the menu, you can use PowerShell transcripts or other text-based logging features. This allows you to maintain control versus only running the PowerShell session with the WindowStyle parameter to hide.

Now begin building script code by calling Start-HideConsole. When the menu-driven script executes, this will ensure the PowerShell console window doesn’t come up.

<# 
	Initialization of functions and objects loading into memory
	Display a text-based loading bar or Write-Progress to the host
#>
 
Start-HideConsole
 
<# 
	Code to display your form/systray icon
	This will hold the console here until closed
 #>

Create Menu Options

Now it’s time to create the menu options. Ensuring you can easily create new options later on, create another function this time called New-MenuItem. When you call this function, it will create a new MenuItem .NET object which you can then add to the menu later.

Each menu option will launch another script or exit the launcher. To accommodate for this functionality,  the New-MenuItem function has three parameters:

  • Text – The label the user will click on
  • MyScriptPath – The path to the PowerShell script to execute
  • ExitOnly – The option to exit the launcher.

Add the below function snippet to the menu script.

 function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )         

Continuing on building the New-MenuItem function, create a MenuItem object by assigning it to a variable.

 #Initialization
 $MenuItem = New-Object System.Windows.Forms.MenuItem

Next, assign the text label to the menu item.

 # Apply desired text
 if($Text) {
 	$MenuItem.Text = $Text
 }

Now add a custom property to the MenuItem called MyScriptPath. This path will be called upon when the item is clicked in the menu.

 #Apply click event logic
 if($MyScriptPath -and !$ExitOnly){
 	$MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty

Add a click event to the MenuItem that launches the desired script. Start-Process provides a clean way to do this within a try/catch block so that you can make sure any errors launching the script (such as PowerShell not being available or the script not existing at the provided path) fall to your catch block.

   $MenuItem.Add_Click({
        try{
            $MyScriptPath = $This.MyScriptPath #Used to find proper path during click event
            
            if(Test-Path $MyScriptPath){
                Start-Process -FilePath "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -ArgumentList "-NoProfile -NoLogo -ExecutionPolicy Bypass -File `"$MyScriptPath`"" -ErrorAction Stop
            } else {
                throw "Could not find at path: $MyScriptPath"
            }
        } catch {
          $Text = $This.Text
          [System.Windows.Forms.MessageBox]::Show("Failed to launch $Text`n`n$_") > $null
        }
  })

Sdd the remaining logic to provide an exit condition for the launcher followed by returning your newly created MenuItem back to be assigned to another variable at runtime.

    #Provide a way to exit the launcher
    if($ExitOnly -and !$MyScriptPath){
        $MenuItem.Add_Click({
            $Form.Close()
    
            #Handle any hung processes
            Stop-Process $PID
        })
    }
 
 	 #Return our new MenuItem
    $MenuItem
 }

You should now have the New-MenuItem function created! The final function should look like this:

  function New-MenuItem{
     param(
         [string]
         $Text = "Placeholder Text",
 
         $MyScriptPath,
         
         [switch]
         $ExitOnly = $false
     )
 
     #Initialization
     $MenuItem = New-Object System.Windows.Forms.MenuItem
 
     #Apply desired text
     if($Text){
         $MenuItem.Text = $Text
     }
 
     #Apply click event logic
     if($MyScriptPath -and !$ExitOnly){
         $MenuItem | Add-Member -Name MyScriptPath -Value $MyScriptPath -MemberType NoteProperty
     }
 
     $MenuItem.Add_Click({
             try{
                 $MyScriptPath = $This.MyScriptPath #Used to find proper path during click event
             
                 if(Test-Path $MyScriptPath){
                     Start-Process -FilePath "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -ArgumentList "-NoProfile -NoLogo -ExecutionPolicy Bypass -File `"$MyScriptPath`"" -ErrorAction Stop
                 } else {
                     throw "Could not find at path: $MyScriptPath"
                 }
             } catch {
                 $Text = $This.Text
                 [System.Windows.Forms.MessageBox]::Show("Failed to launch $Text`n`n$_") > $null
             }
         })
 
     #Provide a way to exit the launcher
     if($ExitOnly -and !$MyScriptPath){
         $MenuItem.Add_Click({
                 $Form.Close()
    
                 #Handle any hung processes
                 Stop-Process $PID
             })
     }
 
     #Return our new MenuItem
     $MenuItem
 }

Test the New-MenuItem function by copying and pasting the above code into your PowerShell console and running the function providing some fake parameter values. You’ll see that a .NET MenuItem object is returned.

 PS51> (New-MenuItem -Text "Test" -MyScriptPath "C:\\test.ps1").GetType()
 
 IsPublic IsSerial Name                                     BaseType
 -------- -------- ----                                     --------
 True     False    MenuItem                                 System.Windows.Forms.Menu

Creating A Launcher Form

Want more tips like this? Check out my personal PowerShell blog at: https://nkasco.com/FriendsOfATA

Now that you can easily create new menu items, it’s time to create a system tray launcher which will display the menu.

Create a basic form object to add components to. This doesn’t need to be anything fancy as it will be hidden to the end user and will keep the console running in the background as well.

 #Create Form to serve as a container for our components
 $Form = New-Object System.Windows.Forms.Form
 ​
 #Configure our form to be hidden
 $Form.BackColor = "Magenta" #Match this color to the TransparencyKey property for transparency to your form
 $Form.TransparencyKey = "Magenta"
 $Form.ShowInTaskbar = $false
 $Form.FormBorderStyle = "None"

Next, create the icon that will show up in the system tray. Below I’ve chosen to use the PowerShell icon. At runtime, the below code creates an actual system tray icon. This icon can be customized to your liking by setting the SystrayIcon variable to your desired icon.

Check out the documentation for the System.Drawing.Icon class to see other methods in which you can load an icon into memory.

 #Initialize/configure necessary components
 $SystrayLauncher = New-Object System.Windows.Forms.NotifyIcon
 $SystrayIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:\\windows\\system32\\WindowsPowerShell\\v1.0\\powershell.exe")
 $SystrayLauncher.Icon = $SystrayIcon
 $SystrayLauncher.Text = "PowerShell Launcher"
 $SystrayLauncher.Visible = $true

When the script is run, you should then see a PowerShell icon show up in your system tray as you can see below.

Now, create a container for your menu items with a new ContextMenu object and create all of your menu items. For this example, the menu will have two scripts to run and an exit option.

 $ContextMenu = New-Object System.Windows.Forms.ContextMenu
 ​
 $LoggedOnUser = New-MenuItem -Text "Get Logged On User" -MyScriptPath "C:\\scripts\\GetLoggedOn.ps1"
 $RestartRemoteComputer = New-MenuItem -Text "Restart Remote PC" -MyScriptPath "C:\\scripts\\restartpc.ps1"
 $ExitLauncher = New-MenuItem -Text "Exit" -ExitOnly

Next, add all of the menu items just created to the context menu. This will ensure each menu option shows up in the form context menu.

 #Add menu items to context menu
 $ContextMenu.MenuItems.AddRange($LoggedOnUser)
 $ContextMenu.MenuItems.AddRange($RestartRemoteComputer)
 $ContextMenu.MenuItems.AddRange($ExitLauncher)
 ​
 #Add components to our form
 $SystrayLauncher.ContextMenu = $ContextMenu

Show the Launcher Form

Now that the form is complete, the last thing to do is to show it while ensuring the PowerShell console window doesn’t come up. Do this by using your Start-HideConsole , displaying the launcher form and then showing the console again withStart-ShowConsole to prevent a hung powershell.exe process.

#Launch
Start-HideConsole
$Form.ShowDialog() > $null
Start-ShowConsole

The full code in its entirety can be found here: https://github.com/nkasco/PSSystrayLauncher

or below as a text file. Please convert to ps1 to run

Creating a Windows 10 Mandatory Profile

I’ve been doing some EUC projects lately and had a requirement to do a Mandatory profile. As there were a number of little bugs in the version I was capturing (Windows 10 64 bit 1803) I’m putting some of the answers here to keep them together for future use. These have been compiled from various sources including the fantastic James Rankin to whom I owe many thanks for discovering and sharing the most complex steps in this process.

I’ll start off with the workarounds and prerequisites, as there are a number of them, including the requirement to create an unattend.xml file for sysprep to copy over default profile which can then be exported to a mandatory profile.


Installing Windows ADK Setup fails

First off, the very annoying bug which causes the install of the Windows 10 Windows Assessment and Deployment Kit (ADK), which we will use later for creating the unattend.xml answer file, to fail and roll back with little information. This is overcome by running it with PSExec from SysInternals (HERE).

The Windows ADK can be downloaded HERE and installed on a machine where you will have access to the Windows 10 install.wim, this can either be on the Windows 10 profile capture VM, or just your normal machine.

First step, download PSExec, from the above, and extract it somewhere convenient (I used c:\psexec\). Then we need to copy the downloaded ADK into this folder.

Open a powershell window and change into your psexec folder and run the following command

.\PsExec.exe /sid powershell.exe

In the new powershell 1.0 window that opens, change into your psexec directory again and run the ADK installer

.\adksetup.exe

Now complete the installer as normal, but be amazed that it now doesn’t rollback and actually installed. Hurrar.

We only need to install the “Deployment Tools” option, so unselect everything else and install.


Convert Install.ESD to Install.WIM

If you’re not using Windows Enterprise, chances are the install.wim file isn’t in the DVD sources directory, but there is an install.esd. So how do you get the wim file needed for creating the unattend.xml answer file?

First things first, we need to copy the install.esd from <DVD>:\sources\install.esd to a read/write folder, the easiest is the root of c:\

Once we’ve got the esd file we need to run a dism command to find the correct image for our install and then extract that image from the esd into a wim

dism /Get-WimInfo /WimFile:install.esd

Take a note of the number which corresponds to your version, for example on my DVD Windows 10 Pro is Index “6”. Now we know which image, lets extract our wim

dism /export-image /SourceImageFile:install.esd /SourceIndex:6 /DestinationImageFile:install.wim /Compress:max /CheckIntegrity

If you receive an error, then change the /Compress variable from “max” to “fast”, “none” or “recovery”.


Creating the unattend.xml answer file

Now we can create the unattend.xml answer file I alluded to earlier, in order to tell sysprep what to do. 

Firstly run “Windows System Image Manager” which we installed as part of the ADK earlier. From the File menu, choose “New Answer File”. On the pop up messagebox click [Yes] and then point to the install.wim file we copied to our c:\ drive earlier. Click [Yes] to create a catalogue file.

In the bottom left “Components” section, right click on the item starting with “amd64_Microsoft-Windows_Shell-Setup” and select “Add Setting to Pass 4 specialize” which will add it to the “Answer File” section

Click on this heading under “Specialize” and set the CopyProfile property on the right-hand pane to “true”. Set any other options within the answer file you wish, however we only need this one.

Validate the answer file by selecting Tools –> Validate answer file, and then save it to the root of c:\ drive as “unattend.xml”

If you built this on another machine, now copy the file to the root of your capture VM.

If you have no network access, there is always the PowerCLI applet Copy-VMGuestFile. However this will require a local user account and VMTools installed so will not work in Audit Mode.

Now we’ve finished discussing prerequisites, lets go look at creating our new profile.

In case you have problems creating the unattend.xml this is a precanned one for Windows 10 1803 Enterprise. It may well work on other versions as well.

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <CopyProfile>true</CopyProfile>
        </component>
    </settings>
    <cpi:offlineImage cpi:source="wim:c:/install.wim#Windows 10 Enterprise" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>

And this is one for Windows 10 1803 Pro

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <CopyProfile>true</CopyProfile>
        </component>
    </settings>
    <cpi:offlineImage cpi:source="wim:c:/install.wim#Windows 10 Pro" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend>

Creating a custom default profile 

Due to the changed way of capturing a mandatory profile in Windows 10, we are now required to create a custom default profile with which to generate our mandatory profile.

The first step is to deploy a new VM, install Windows 10 to the configuration point, and enter Audit Mode.

So when you get to the above screen after installing Windows 10 you need to hammer Ctrl + Shift + F3 which will log you in as Administrator in Audit Mode.

Cancel the sysprep dialog box and make any changes you want on a device or user level. You can remove the Universal Windows Platform (UWP) apps here as well, I created an article on this HERE. Typical user level changes are:

  • Setting the background image and branding
  • Changing Explorer to open “my PC” instead of “Quick Access”
  • Show file extensions
  • Create any desktop icons as required
  • Pin Taskbar items
  • Arrange and remove the Start tiles
  • Configure IE/Edge homepage

Once all this is done we need to export the Start tile layout

Export-StartLayout -Path $ENV:LOCALAPPDATA\Microsoft\Windows\Shell\LayoutModification.xml

If you haven’t already created the unattend.xml answer file, install the Windows ADK and create it now as above. If you’ve created it on another machine, copy it to the root of the c:\ drive

Once we’ve created the answer file and put it on the root of the capture VM, we need to run sysprep.

c:\windows\system32\sysprep\sysprep.exe /oobe /generalize /shutdown /unattend:c:\unattend.xml

If sysprep fails to run for any reason, check the log file, but the most likely culprit will be a UWP which was removed from one place but not the other. The log file will detail which one is causing the conflict.

Once complete the VM will shutdown with the new default profile.

Additionally we can shrink down this default profile using the following commands from a powershell or cmd window by restarting the VM, and either entering audit mode again or completing sysprep.

takeown /f c:\users\default\appdata\local\Microsoft\WindowsApps /r /a /d Y
icacls c:\users\default\appdata\local\Microsoft\WindowsApps /grant Administrators:F /T /C /L
get-childitem C:\Users\Default\AppData\LocalLow -force | foreach ($_) {remove-item $_.fullname -force -recurse -confirm:$false}
get-childitem C:\Users\Default\AppData\Local\Microsoft\Windows -exclude “Shell”,”WinX” -Force | foreach ($_) {remove-item $_.fullname -force -recurse -confirm:$false}
get-childitem C:\Users\Default\AppData\Local\Microsoft -exclude “Windows” -Force | foreach ($_) {remove-item $_.fullname -force -recurse -confirm:$false}
get-childitem C:\Users\Default\AppData\Local -exclude “Microsoft” -Force | foreach ($_) {remove-item $_.fullname -force -recurse -confirm:$false}
get-childitem C:\Users\Default\AppData\Roaming\Microsoft\Windows -exclude “Start Menu”,”SendTo” -Force | foreach ($_) {remove-item $_.fullname -force -recurse -confirm:$false}
get-childitem C:\Users\Default\AppData\Roaming\Microsoft -exclude “Windows” -Force | foreach ($_) {remove-item $_.fullname -force -recurse -confirm:$false}
get-childitem C:\Users\Default\AppData\Roaming -exclude “Microsoft” -Force | foreach ($_) {remove-item $_.fullname -force -recurse -confirm:$false}
Get-ChildItem c:\users\default -Filter “*.log*” -Force | Remove-Item -Force
Get-ChildItem c:\users\default -Filter “*.blf*” -Force | Remove-Item -Force
Get-ChildItem c:\users\default -Filter “*.REGTRANS-MS” -Force | Remove-Item -Force

We could leave this here which would be suitable for many VDI installs, however I will go on with the creation of the mandatory profile.


Creating a mandatory profile

Having completed all of the steps from above, log on to the VM as an administrative user.

Open the Advanced section of System settings (either from Control Panel, Win + Break or Right click on My PC and click Properties). Click on the Advanced tab, and click on Settings to get to the User Profiles dialogue.

  • Click on the Default Profile and click [Copy To].
  • Fill in the “Copy profile to” location with the folder where you would like to store the mandatory profile.
    • I store it on the root of the c:\ drive and call the folder “mandatory.v6” as 1803 has profile version 6
  • Check the “Mandatory profile” checkbox.
  • Click [Change] and add “Authenticated Users” into the Object field.
  • Click [OK] and the folder you have specified as a destination will be created.

Weirdly there is no success message and the window remains open after clicking [OK], so hit [Cancel] to exit the dialog box.


Bug Time!

So there’s a weird bug in at least this version I’m using (1803) where it doesn’t copy the ntuser.dat and .ini files into the new folder. Helpful. If this happens to you, just go ahead and copy these two files from c:\users\default into your new folder.


Set permissions on filesystem

Now we’ve got our mandatory profile, we need to check that the filesystem permissions are correct. The copy will have added “Authenticated Users” with Read and Execute permissions, but we also need to add the “All Application Packages” user with “Full Control” and ensure that the Administrators group owns the folder, along with all subfolders.

So add these permissions and set them to replace all child objects and press [OK]


Set permissions on Registry

We also need to set the same permissions for the Registry. So lets fire up regedit, click on the HKEY_USERS hive, go to File –> Load Hive and select the ntuser.dat file from our mandatory profile folder.

Give the hive a temporary name (this doesn’t affect anything) and the hive name will now show under HKEY_USERS.

  • Right-click on the root of the hive and select Permissions.
  • Modify the permissions to allow both “Authenticated Users” and “All Application Packages” to have “Full Control”
  • Click [OK] and [OK] again on the error that pops up.

Clean up the Registry

Now we can clean up the Registry to trim it down a bit.

  • Remove all references to the Administrator username from the Registry hive using Edit –>Find
  • Delete any Registry keys or values that you deem unnecessary. For example:
    • any Policies keys under HKCU\Software
    • HKCU\Software\Microsoft\Windows\CurrentVersion.
    • HKCU\Software\AppDataLow
    • Other people have managed to massively reduce the ntuser.dat by being ruthless. I will have to test how far we can go but feel free to follow their suggestions.

Once complete you must remember to unload the hive by selecting the hive name you chose and going to File –> Unload Hive

If you don’t do this, you will lock the profile meaning no one will be able to access it.

After unloading the hive there will be a number of new *.log* and *.blf files in the folder, these can be deleted.


Making it Mandatory

To make this profile “mandatory” we need to rename the ntuser.dat file to ntuser.man. Additionally you can rename the folder to mandatory.man.v6 to make it super-mandatory! This just means that a temporary profile will never be used.


Finally some Group Policy

Setting the mandatory profile in group policy requires us to set the following:

Computer Configuration –> Policies –> Administrative Templates –> System –> User Profiles

Set “Set roaming profile path for all users logging onto this computer” to the profile location but omit the .V6 extension.

In addition to applying the mandatory profile, we need to set one additional option, else you will suffer from a broken Start Menu.

Computer Configuration –> Administrative Templates –> Windows Components –> App Package Deployment

Set “Allow deployment operations in special profiles” to Enabled.

Don’t forget to enable loop back as well!

Computer Configuration –> Policies –> Administrative Templates –> System –> Group Policy –> Configure user Group Policy loopback processing mode.

And we’re done!