What’s behind ‘Run with PowerShell’ context menu?

To find out what the “Run with PowerShell” action does, let’s dig into the registry:
run-with-powershell-context-menu

.ps1 file exetension is associated with the Microsoft.PowerShellScript.1 type.
reg-ps1-extension-assoc

I can find the “Run with PowerShell” string into the cache.
PS-MUIcache

Using the verb, it clearly uses the registry key named 0 and its command below
ps1-mui-verb

I can see what action will be performed on the file with I click ‘Run with PowerShell’:

(Get-ItemProperty -Path "HKLM:\SOFTWARE\Classes\Microsoft.PowerShellScript.1\Shell\0\Command").'(default)'

It will do the following: (%1 represents the location (fullpath) of the ps1 file being launched)

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" "-Command" "if((Get-ExecutionPolicy ) -ne 'AllSigned') { Set-ExecutionPolicy -Scope Process Bypass }; & '%1'"

If the Execution policy is set to AllSigned, there’s no attempt to modify the execution policy in the process scope.

In my case, this prompt is expected considering what the ‘run with PowerShell’ action does
Run-with-powerhshell-policy-prompt

But sometimes, I don’t get a prompt. Strange😦

There’s an about file about_Run_With_PowerShell that states:

The “Run with PowerShell” feature starts a Windows PowerShell
session that has an execution policy of Bypass, runs the
script, and closes the session.

It runs a command that has the following format:
PowerShell.exe -File -ExecutionPolicy Bypass

“Run with PowerShell” sets the Bypass execution policy only
for the session (the current instance of the PowerShell process)
in which the script runs. This feature does not change the execution
policy for the computer or the user.

The “Run with PowerShell” feature is affected only by the AllSigned
execution policy. If the AllSigned execution policy is effective for
the computer or the user, “Run with PowerShell” runs only signed
scripts. “Run with PowerShell” is not affected by any other execution
policy. For more information, see about_Execution_Policies.

Troubleshooting Note: Run with PowerShell command might prompt you
to confirm the execution policy change.

If I compare to what I see in the registry, Powershell.exe isn’t invoked with the -File parameter and the -Executionpolicy parameter set to bypass.
Also note that the help file states that the command might prompt you to confirm the execution policy change.
“Might” actually means: it’s true, I can get a prompt, but it’s not very likely.
This is actually the definition of an inconsistent behavior.

My Applocker policy seems to catch and block the execution.
run-with-ps-8007-event

But, I also immediatly get the event 4104 in the Microsoft-Windows-PowerShell/Operational as I turned on (non protected) scriptblock logging
run-with-ps-4104-event

Whether I get a prompt or not, whether I answer yes or no to the prompts, the code inside the ps1 file is executed.
The code being executed is a single WMI query that requires administrative privileges.
Its execution ends with a terminating error because the parent process of PowerShell.exe is Explorer and PowerShell.exe inherits from its integrity level set to “Medium”, running in the standard user context. (i.e. not running as administrator).

Anyway, I also turned on Transcripts and I clearly get the confirmation that the code inside .ps1 file is executed:

run-with-ps-transcript-N

run-with-ps-transcript-Y

PowerShell scriptblock logging and transcripts are more reliable than Applocker.
“Run with PowerShell” actually bypassed the Appplocker policy by launching powershell.exe -command “& ”” and dot sourcing the script the same way malware do in their post-exploitation phase.

Better safe than sorry, I changed the behavior of the “Run with PowerShell” context menu.
I set it to the default behavior when you double-click on a .ps1 file: notepad.exe opens it, nothing is executed.

Set-ItemProperty -Path `
"HKLM:\SOFTWARE\Classes\Microsoft.PowerShellScript.1\Shell\0\Command" `
-Name '(default)' -Value '"C:\Windows\System32\notepad.exe" "%1"'

Anybody logging on this machine will now benefit from the new behavior.

Backup/Restore a local Windows Internal Database

I’ve got Direct Access servers where I want to backup the local Windows Internet Database.

The built-in VSS (Volume Shadow Copy Services) has 3 components:
VSS-components

Let’s see if there’s a writer for the Windows Internal Database component (WID)

vssadmin list writers | 
Select-String -Pattern "Writer\sname:\s" -Context 0,3 |
ForEach-Object {
    [pscustomobject]@{
        Id = (($_.Context.PostContext -split "\r\n")[0] -split ':')[1].Trim()
        Name = ($_.Line -split ':\s',2)[1] -replace "'",''
    }
} | Select Name,Id | ft -AutoSize

VSS-writers
Fortunately, there’s a WIDWriter in the above list😀

It also means that the built-in tools are able perform a live backup. But, I first need to add the Windows Backup feature:

Add-WindowsFeature Windows-Server-Backup  -Restart:$false

Now, I’m ready to backup and restore the internal database files.
The backup operation will create a WindowsImageBackup directory on the target partition (P: in my case).
The recovery operation will extract files from the VHD previously created. It will not restore files in their original location under C:\Windows\…
but will rather move them to another folder (P:\BackupDB) and preserve the original folders tree.
VSS-restore-location

Quick ‘n dirty solution but it allowed me to extract the database files in a consistent way using only the built-in tools and the capabilities of the Volume Shadow Copy Services (VSS).

Import the convenience update into WSUS

My WSUS server runs on a Windows Core edition where I don’t have Internet Explorer installed.

Whatif I want to import the Convenience rollup update for Windows 7 SP1 and Windows Server 2008 R2 SP1 from the catalog?

Here’s the way to go:

On a client computer with a GUI, launch internet explorer with high privileges to be able to install the ActiveX.

$HT = @{
 FilePath = 'C:\Program Files (x86)\Internet Explorer\iexplore.exe' ;
 ArgumentList = 'http://catalog.update.microsoft.com/v7/site/Search.aspx?q=KB3125574' ;
 Verb = 'Runas'
}
Start-Process @HT

MS-catalog-ActiveX-install

Install the ActiveX, add the update to the basket, view your basket, download, browse…
Move the file onto the WSUS server…and check if the file is digitally signed

Get-AuthenticodeSignature -FilePath ~\downloads\Catalog\*\*.msu

The WSUS API has an ImportUpdateFromCatalogSite, documented here on msdn.
WSUS-import-method

I’m missing the UpdateID. Where do I find this Id?

It appears that if you click on the link highlighted below…

MS-catalog-KB-link

…you get on a page where you’ve more details about the update.

Convenience-update-Id

The updateId actually appears in the address bar.😀

Now, to import the update into WSUS, I do:

$MSUfile = 'C:\Users\administrator\Downloads\Catalog\Update for Windows 7 for x64-based Systems (KB3125574)\AMD64-all-windows6.1-kb3125574-v4-x64_2dafb1d203c8964239af3048b5dd4b1264cd93b9.msu'
(Get-WsusServer).ImportUpdateFromCatalogSite(
    '49924c88-1b31-4b0f-ad3d-48df9877f385',
    $MSUfile
)
# Find the imported file
(Get-WsusServer).SearchUpdates('3125574') | fl *

The convenience update has been successfully imported and can now be approved.

Fix GPO permissions before applying MS16-072

While monitoring the PM.org mailing list yesterday, a problem with Group Policies was reported.

The problem was quickly identified on this Microsoft forum thread and the fix was documented a few hours later on this support page.

To quickly display GPO names that don’t have the Authenticated Users group, you can do:

Get-GPO -All | ForEach-Object { 
    # Test if Authenticated Users group have at least read permission on the GPO
    if ('S-1-5-11' -notin ($_ | Get-GPPermission -All).Trustee.Sid.Value) {
        $_
    }
} | Select DisplayName

To add back the Authenticated Users group with Read Permissions on the Group Policy Object (GPO), you can do:

Get-GPO -All | ForEach-Object { 
    if ('S-1-5-11' -notin ($_ | Get-GPPermission -All).Trustee.Sid.Value) {
        $_ | Set-GPPermission -PermissionLevel GpoRead -TargetName 'Authenticated Users' -TargetType Group -Verbose
    }
}

Now, every GPO has a permission set for the ‘Authenticated Users’ group and to check what permission is set for this group, you can do:

Get-GPO -All | ForEach-Object { 
    [PsCustomObject]@{
        DisplayName = $_.DisplayName
        Permission = ($_ | Get-GPPermission -TargetName 'Authenticated Users' -TargetType Group).Permission
    }
} | Out-GridView -Title 'Authenticated Users permissions'

Any (documented) ADSI changes in PowerShell 5.0?

I’ve been using for years the following ADSI code to query members of the local administrators group:

@(
([ADSI]"WinNT://./Administrators").psbase.Invoke('Members') |
% { 
 $_.GetType().InvokeMember('AdsPath','GetProperty',$null,$($_),$null) 
 }
) -match '^WinNT'

It works w/o any problem from PowerShell 2.0 to 4.0 and there are many code out there using this syntax.

I’ve noticed that there’s now a problem with the above syntax on PowerShell 5.0 running on Windows 7 or Windows 10.

The problem is precisely when the member is user. When we use the GetType() method, it reports:
Error while invoking GetType. Could not find member. Weird MissingMemberException😦
When the member is a group, invoking GetType() still works.

How to fix this?
The best option is to have a piece of code that is compatible with any version of PowerShell and that doesn’t use the GetType() method invocation.

What about?

([ADSI]"WinNT://./Administrators").psbase.Invoke('Members') | % {
 ([ADSI]$_).InvokeGet('AdsPath')
}

Isn’t it more simple and less obscure?😀

After using my Google-fu, some people reported this issue on stackoverflow.com about a year ago and the bug was also submitted to Microsoft on this page. What about also voting for it?

Quick tip: test if Windows 10 opted-in the Windows Insider Preview program

If you haven’t blocked the ability for users to join the Windows Insider Preview program with the following GPO policy, Toggle-IPB-button you may need to quickly test if their Windows 10 opted-in the Windows Insider Preview program.

Here’s a simple and fast way of testing it:

# load the function
Function Test-isPreviewBuild {
    $HT = @{
        Path = 'HKLM:\SOFTWARE\Microsoft\WindowsSelfHost\Applicability' ;
        Name = 'EnablePreviewBuilds' ;
        ErrorAction = 'SilentlyContinue' ;
    }
    [bool](Get-ItemProperty @HT).EnablePreviewBuilds
}

# invoke the function
Test-isPreviewBuild

Note about the Convenience rollup update KB3125574 on Windows 7

If you don’t know yet what is the Convenience rollup update KB3125574 for Windows 7 that hit the headlines, I’d encourage you to read the following blog post:

https://blogs.technet.microsoft.com/windowsitpro/2016/05/17/simplifying-updates-for-windows-7-and-8-1/

If you plan to do anything with it, please also read its related KB page: https://support.microsoft.com/en-us/kb/3125574 . There are more info about known issues, the list of files changed (stored in a CSV file attached).

I’ve quickly tested it on some Windows 7 computers.
If you already have WMF 5.0, there’s no change to the $PSVersionTable.
But, if you’ve a vanilla Windows 7 installation and apply the “SP2”, you’ll shift from
PS2-W7-sp1
to
PS2-W7-sp2
Did you notice that both the BuildVersion and the CLRVersion increased respectively from 6.1.7601.17514 to 6.1.7601.23403 and from 2.0.50727.5485 to 2.0.50727.8669?

If you’ve some code testing the values in the $PSVersionTable, you should consider revising your code and verifying the way your code handles theses values.