Sunday, April 28, 2013

Out-Clipboard Cmdlet

PowerShell doesn’t provide a cmdlet to set the clipboard with the output of your PowerShell console commands, so I’ve created an Out-Clipboard cmdlet which is extemelly handy! You’ll definitely want to store this one away in your PowerShell profile.

This script cmdlet will take pipeline input and store the text created by PowerShell's object formatting engine in your clipboard. If you have files in your pipeline and want those to be copied as items that can be pasted in Windows Explorer, set the -File switch parameter.

Windows 7 comes with clip.exe that could be used to do something similar (except pasting files in Windows Explorer), however since PowerShell uses the age old matrix based Windows console, each line of text will have space padding at the end to fill up the console line buffer. What we really want is just the actual text of stdout copied to the clipboard, not the line ending padding as well. This cmdlet will take care of removing the padding for you.

The cmdlet was a little tricky to create because the System.Windows.Forms.Clipboard type. It's tricky because it requires the AppDomain to be running as a Single-Threaded Apartment (STA), whereas the PowerShell 2.0 console runs in a MTA AppDomain. By the way, in PowerShell 2.0, the ISE runs in STA and the console in MTA. In PowerShell 3.0 both run in STA by default. This whole apartment mode business is something we only need worry about when dealing with certain classes in .NET that interact with COM objects. Basically the apartment mode of the caller has to match that of the type being called. It won't work if they arn't. There are two ways to interact with this type from a PowerShell console running as MTA:

  1. Create another instance of PowerShell using the -STA parameter
  2. Create a runspace in STA mode

Creating a runspace is faster because another full instance of PowerShell doesn't have to spin up. This cmdlet uses the runspace approach. Since runspaces operate in a different thread, they don't have access to the variables set in the current runspace. We can still pass data to the new runspace however using the $runspace.SessionStateProxy.SetVariable method. Code is added for execution to the new runspace using the AddScript method which takes a scriptblock. The last part is to run the Invoke method on the PowerShell object returned from the AddScript method.

This makes getting text copied from the console to the clipboard so much easier. Before I created this, I used to pipe to Out-GridView which was a pain because takes a long time to initialize and involved a few extra unnecessary steps. Well... Unnessary thanks to Out-Clipboard :D

Sunday, July 15, 2012

Supporting Wildcard Pattern Matching in Script CMDLETS

One of the cool features about PowerShell is its support for POSIX style patterns in many of the pre-installed cmdlets. For example the following command will get all items under the C root that have the second, third and forth characters “rog”:

Outputs:

    Directory: C:\


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d-r--          6/7/2012  11:30 PM            Program Files
d-r--         6/26/2012   1:50 PM            Program Files (x86)

The following table shows a reference of the supported POSIX style matching patterns:

Wildcard character

Description

Example

Matches

Does not match

*

Matches zero or more characters, starting at the specified position

a*

A, ag, Apple

 

?

Matches any character at the specified position

?n

An, in, on

ran

[ ]

Matches a range of characters

[a-l]ook

book, cook, look

took

[ ]

Matches the specified characters

[bc]ook

book, cook

look

So how would one go about adding support for these types of patterns in a script CMDLET? In order to do this you’ll need to work with two .NET types:

  1. System.Management.Automation.WildcardPattern
  2. System.Management.Automation.WildcardOptions

First define your options (Compile, CultureInvariant, IgnoreCase or None). Then use New-Object to create a WildcardPattern instance using the pattern you want to match against as the first argument and the WildcardOptions object as the second argument.

Here is a an example function:

Tuesday, February 21, 2012

Get System DEP Setting with PowerShell

The data execution policy can be obtained using bcdedit /enum however that command has a lot of output. I needed to get the system DEP policy setting in PowerShell so I found a Win32 function called GetSystemDEPPolicy. So in order to call this from PowerShell we'll need to use p/invoke again. Here's how:

function Get-SystemDEPPolicy {
Add-Type -Namespace Win32 -Name DEP -MemberDefinition @"
[DllImport("kernel32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern int GetSystemDEPPolicy();

public static int GetCurrentSystemDEPPolicy() {
return GetSystemDEPPolicy();
}
"@
$policyCode = [Win32.DEP]::GetCurrentSystemDEPPolicy()
switch ($policyCode) {
0 {Write-Output 'AlwaysOff'}
1 {Write-Output 'AlwaysOn'}
2 {Write-Output 'OptIn'}
3 {Write-Output 'OptOut'}
}
}

Update: I should of looked at WMI first! There is a property named DataExecutionPrevention_SupportPolicy in the Win32_OperatingSystem class that also stores this information and it's easier to retrieve...


Get-WmiObject -Class Win32_OperatingSystem -Property DataExecutionPrevention_SupportPolicy

Monday, February 20, 2012

Unblocking Files with PowerShell

When you download a file from the internet you may of seen this:

2012-02-19 01h38_17

If the file is an executable and you try to run it, you will be prompted if you want to run the file… Yea, yea… I just freaking downloaded it, I want to run it already! If you download a ZIP file (such as the Sysinternals Suite) you'll have to unblock every program in it…

So what's really going on is that something called an alternate data stream get's created for the file when you save it to disk. This is a special feature of NTFS that allows a file name to reference more than one data stream on disk. The primary data stream is just the data that the filename is referencing. So Test.zip points to a data stream and that's where the ZIP file data is stored. Alternate data streams are stored and accessed with the name of the file plus ':Data_Stream_Name' (a colon and the name of the alternate stream). These additional streams are hidden from Windows Explorer and the command prompt directory listing. Only special methods allow you to access them. When a file is downloaded from the internet an alternate access stream is created with the name 'Zone.Identifier'. The contents look like this:

ZoneId 3 means it came from the internet. This is what Windows uses to determine whether or not it should block the file. You can edit the file by opening it with Notepad from a command prompt like this:

However you cannot delete the alternate access stream from disk using this approach, only delete or change it's contents. There is a function called DeleteFile in Win32 API that will allow you to delete an alternate access stream however we can't call this from PowerShell directly. We will need to p/invoke using the Add-Type cmdlet.

I've wrapped this into a portable function that allows pipeline input. So you can pipe an entire set of files to this like so:

As I write this, PowerShell 3 is current in CTP2 and it does include a cmdlet for this. However if you are using PowerShell v2 this is the way to go.

Sunday, February 12, 2012

Fixing Unresolvable NTFS ACL Accounts

An interesting question came up on SuperUser the other day. The OP wanted to know how to change the account of a NTFS ACL but still retain all of the ACL settings (privileges, inheritance etc.…). This could be needed if the original account was deleted, or if the ACL was on a drive that was moved to another machine etc.…

When the ACL entry has an unresolvable account it will appear like this:

image

The first thing that came to mind was to edit the SDDL string. The SDDL is a not so easy to read string representation of the ACL. It looks like this:

Easy to read huh?

You can learn all about the format on MSDN, however the format is not important for our purpose here. All we need to do is replace the SID with the SID of the account we want.

So first lets use the Win32_Account WMI class to get a list of user account SIDs:

This will get all account types from the local machine and the domain if the machine is on a domain which could make this command take a really long time.

To make it go faster we’ll filter the query a little bit. There are a couple properties of this class we can use to reduce the run time.

  • Name
  • SidType

    (1) User
    (2) Group
    (3) Domain
    (4) Alias
    (5) WellKnownGroup
    (6) DeletedAccount
    (7) Invalid
    (8) Unknown
    (9) Computer

  • LocalAccount

So to get the SID of a local user account named NewAccount we’ll use:

To find the SID of the broken ACL entry we’ll use the Get-ACL cmdlet. The ACE’s are stored in the Access property returned by the Get-Acl object. To retrieve the SID of the entry which account is unknown we’ll use the Translate method to find that one that can’t translate to an NTAccount object. We also aren’t interested in ACE’s that are inherited so we’ll filter them out.

The invalid SIDs are stored in the array $invalidSids.

Now we can do a simple string search and replace in the SDDL string.

The last step is to set the ACL with the updated SDDL. To do this we’ll use the SetSecurityDescriptorSddlForm method.

And that’s it. The ACL is now updated with the account but all of the previous ACE settings are retained as we wanted.

image

Saturday, January 7, 2012

Changing Tomcat’s CA Trust Keystore File

By default Tomcat makes use of the cacerts file provided with the Java runtime environment located under: C:\Program Files\Java\jre6\lib\security.

Since the JRE installer likes to remove this file when it is updated or uninstalled I needed to find a way to re-configure Tomcat so it uses an different keystore.

System Setup

  1. Start without Java or Tomcat installed.
  2. Install JRE (this is the x64 version): http://download.oracle.com/otn-pub/java/jdk/6u30-b12/jre-6u30-windows-x64.exe
  3. Create JAVA_HOME system wide variable: setx.exe JAVA_HOME "C:\Program Files\Java\jre6" /M
  4. Install Tomcat: http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.29/bin/apache-tomcat-6.0.29.exe
  5. Select all defaults for the Tomcat Normal installation.
  6. Get Portecle to create new and view contents of JKS keystores: http://cdnetworks-us-2.dl.sourceforge.net/project/portecle/portecle/1.7/portecle-1.7.zip

Testing

  1. First we’ll create a test Java webapp that makes an HTTPS connection.
  2. We’ll create a test JSP page (source code at the bottom) using the URLConnection class in Java to connect to https://www.paypal.com/ and fetch its page content.
  3. If the connection is successful the page content is displayed below the “We are connected to HTTPS URL?” message.
  4. If the connection is not successful the exception error message will be displayed.
  5. The test JSP shows we can connect to https://www.paypal.com/ successfully and retrieve it’s content:
image

Why Does This Work?

In order for the URLConnection object to successfully make a connection the certificate from Paypal’s certificate signer must be pre-installed in the JRE cacerts JKS keystore. Let’s make sure…

Paypal’s certificate is signed by a VeriSign certificate.

The SHA1 thumbprint of the VeriSign cert is: 4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5

image

We have this VeriSign certificate pre-installed in the JRE cacerts JKS keystore:

image

More Testing…

First lets verify the test JSP works by removing all certificates from the JRE cacerts keystore and re-trying the test webapp.

We’ll use some PowerShell to remove all certificates from the JRE cacerts JKS keystore.

Resultant empty JRE cacerts keystore:

image

For good measure lets restart Tomcat to make sure our changes take effect with a PowerShell one liner: restart-service tomcat*
After restarting Tomcat the HTTPS connection is now broken!

image

As a sanity check we’ll install only the VeriSign certificate in the JRE cacerts keystore all by itself:

image

Now the HTTPS connection works again!

image

Changing the Tomcat Keystore

Because JRE 6 removes it’s cacerts file when it is uninstalled or upgraded I wanted to re-configure Tomcat to use a different keystore so that my certificates being used for HTTPS endpoints wouldn't be deleted. Figuring out how turned out to be quite an adventure.

First I checked the Tomcat documentation for server.xml and found this:

keystoreFile The pathname of the keystore file where you have stored the server certificate to be loaded. By default, the pathname is the file ".keystore" in the operating system home directory of the user that is running Tomcat. If your keystoreType doesn't need a file use "" (empty string) for this parameter.
truststoreFile The trust store file to use to validate client certificates. The default is the value of thejavax.net.ssl.trustStore system property. If neither this attribute nor the default system property is set, no trust store will be configured.

The keystoreFile setting is for Tomcat’s private key for HTTPS connectors and the truststoreFile setting is for client authentication but hey lets try both!

Below is a connector configured to point to a single JKS keystore with the VeriSign certificate inside. Here is the server.xml connector for port 8080.

… And what is inside of C:\mykeystore.jks:

image

After restarting Tomcat things aren't looking good:

image

The solution

It turns out a couple of Java JVM parameters are needed to point Tomcat to a different keystore.

These can be set via the Tomcat configuration utility: "C:\Program Files\Apache Software Foundation\Tomcat 6.0\bin\tomcat6w.exe" //ES//Tomcat6

image

This tool stores values in the registry: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Apache Software Foundation\Procrun 2.0\Tomcat6\Parameters\Java\Options

image

The Tomcat connector was reverted back to the default:

After restarting Tomcat again… The final result: Success! Tomcat is now using C:\mykeystore.jks instead of the JRE cacerts keystore.

image

test.jsp Source Code

Monday, October 3, 2011

PowerShell: Set-SecureAutoLogon

The module that controls the Windows login experience is called MSGina.dll. Code in this module is responsible for providing the Windows login screen. This code reads values from the registry key to control the experience:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

Specifically the following values are important:

AutoAdminLogon

AutoLogonCount

DefaultUserName

DefaultPassword

DefaultDomainName

By setting the values above appropriately, you may enable automatic login for a particular user account. However DefaultPassword is a clear text string! Security vulnerabilities raised by the fact of enabling automatic login aside, the password is readable to anyone after the automatic login happens!

There is a way to store the password in a more secure way using the LsaStorePrivateData function from the Advapi32.lib Win32 module. This is a native code API which is not easily accessible to PowerShell. There is however a way to call this function which involves p/inoking (platform invoking) the native API using compiled code (c#/vb.net) using the Add-Type cmdlet introduced in PS v2. It is possible to do this PS v1 however it requires a lot more code.

So without further ado, see the Set-SecureAutoLogon fully implemented below.

[cmdletbinding()]
param (
[Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string]
$Username,

[Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [System.Security.SecureString]
$Password,

[string]
$Domain,

[Int]
$AutoLogonCount,

[switch]
$RemoveLegalPrompt,

[System.IO.FileInfo]
$BackupFile
)

begin {

[string] $WinlogonPath = "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon"
[string] $WinlogonBannerPolicyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"

[string] $Enable = 1
[string] $Disable = 0

#region C# Code to P-invoke LSA LsaStorePrivateData function.
Add-Type @"
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace ComputerSystem
{
public class LSAutil
{
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}

[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}

private enum LSA_AccessPolicy : long
{
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
POLICY_TRUST_ADMIN = 0x00000008L,
POLICY_CREATE_ACCOUNT = 0x00000010L,
POLICY_CREATE_SECRET = 0x00000020L,
POLICY_CREATE_PRIVILEGE = 0x00000040L,
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
POLICY_SERVER_ADMIN = 0x00000400L,
POLICY_LOOKUP_NAMES = 0x00000800L,
POLICY_NOTIFICATION = 0x00001000L
}

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaRetrievePrivateData(
IntPtr PolicyHandle,
ref LSA_UNICODE_STRING KeyName,
out IntPtr PrivateData
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaStorePrivateData(
IntPtr policyHandle,
ref LSA_UNICODE_STRING KeyName,
ref LSA_UNICODE_STRING PrivateData
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaOpenPolicy(
ref LSA_UNICODE_STRING SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
uint DesiredAccess,
out IntPtr PolicyHandle
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaNtStatusToWinError(
uint status
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaClose(
IntPtr policyHandle
);

[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern uint LsaFreeMemory(
IntPtr buffer
);

private LSA_OBJECT_ATTRIBUTES objectAttributes;
private LSA_UNICODE_STRING localsystem;
private LSA_UNICODE_STRING secretName;

public LSAutil(string key)
{
if (key.Length == 0)
{
throw new Exception("Key lenght zero");
}

objectAttributes = new LSA_OBJECT_ATTRIBUTES();
objectAttributes.Length = 0;
objectAttributes.RootDirectory = IntPtr.Zero;
objectAttributes.Attributes = 0;
objectAttributes.SecurityDescriptor = IntPtr.Zero;
objectAttributes.SecurityQualityOfService = IntPtr.Zero;

localsystem = new LSA_UNICODE_STRING();
localsystem.Buffer = IntPtr.Zero;
localsystem.Length = 0;
localsystem.MaximumLength = 0;

secretName = new LSA_UNICODE_STRING();
secretName.Buffer = Marshal.StringToHGlobalUni(key);
secretName.Length = (UInt16)(key.Length * UnicodeEncoding.CharSize);
secretName.MaximumLength = (UInt16)((key.Length + 1) * UnicodeEncoding.CharSize);
}

private IntPtr GetLsaPolicy(LSA_AccessPolicy access)
{
IntPtr LsaPolicyHandle;

uint ntsResult = LsaOpenPolicy(ref this.localsystem, ref this.objectAttributes, (uint)access, out LsaPolicyHandle);

uint winErrorCode = LsaNtStatusToWinError(ntsResult);
if (winErrorCode != 0)
{
throw new Exception("LsaOpenPolicy failed: " + winErrorCode);
}

return LsaPolicyHandle;
}

private static void ReleaseLsaPolicy(IntPtr LsaPolicyHandle)
{
uint ntsResult = LsaClose(LsaPolicyHandle);
uint winErrorCode = LsaNtStatusToWinError(ntsResult);
if (winErrorCode != 0)
{
throw new Exception("LsaClose failed: " + winErrorCode);
}
}

public void SetSecret(string value)
{
LSA_UNICODE_STRING lusSecretData = new LSA_UNICODE_STRING();

if (value.Length > 0)
{
//Create data and key
lusSecretData.Buffer = Marshal.StringToHGlobalUni(value);
lusSecretData.Length = (UInt16)(value.Length * UnicodeEncoding.CharSize);
lusSecretData.MaximumLength = (UInt16)((value.Length + 1) * UnicodeEncoding.CharSize);
}
else
{
//Delete data and key
lusSecretData.Buffer = IntPtr.Zero;
lusSecretData.Length = 0;
lusSecretData.MaximumLength = 0;
}

IntPtr LsaPolicyHandle = GetLsaPolicy(LSA_AccessPolicy.POLICY_CREATE_SECRET);
uint result = LsaStorePrivateData(LsaPolicyHandle, ref secretName, ref lusSecretData);
ReleaseLsaPolicy(LsaPolicyHandle);

uint winErrorCode = LsaNtStatusToWinError(result);
if (winErrorCode != 0)
{
throw new Exception("StorePrivateData failed: " + winErrorCode);
}
}
}
}
"@
#endregion
}

process {

try {
$ErrorActionPreference = "Stop"

$decryptedPass = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
)

if ($BackupFile) {
# Initialize the hash table with a string comparer to allow case sensitive keys.
# This allows differentiation between the winlogon and system policy logon banner strings.
$OrigionalSettings = New-Object System.Collections.Hashtable ([system.stringcomparer]::CurrentCulture)

$OrigionalSettings.AutoAdminLogon = (Get-ItemProperty $WinlogonPath ).AutoAdminLogon
$OrigionalSettings.ForceAutoLogon = (Get-ItemProperty $WinlogonPath).ForceAutoLogon
$OrigionalSettings.DefaultUserName = (Get-ItemProperty $WinlogonPath).DefaultUserName
$OrigionalSettings.DefaultDomainName = (Get-ItemProperty $WinlogonPath).DefaultDomainName
$OrigionalSettings.DefaultPassword = (Get-ItemProperty $WinlogonPath).DefaultPassword
$OrigionalSettings.AutoLogonCount = (Get-ItemProperty $WinlogonPath).AutoLogonCount

# The winlogon logon banner settings.
$OrigionalSettings.LegalNoticeCaption = (Get-ItemProperty $WinlogonPath).LegalNoticeCaption
$OrigionalSettings.LegalNoticeText = (Get-ItemProperty $WinlogonPath).LegalNoticeText

# The system policy logon banner settings.
$OrigionalSettings.legalnoticecaption = (Get-ItemProperty $WinlogonBannerPolicyPath).legalnoticecaption
$OrigionalSettings.legalnoticetext = (Get-ItemProperty $WinlogonBannerPolicyPath).legalnoticetext

$OrigionalSettings | Export-Clixml -Depth 10 -Path $BackupFile
}

# Store the password securely.
$lsaUtil = New-Object ComputerSystem.LSAutil -ArgumentList "DefaultPassword"
$lsaUtil.SetSecret($decryptedPass)

# Store the autologon registry settings.
Set-ItemProperty -Path $WinlogonPath -Name AutoAdminLogon -Value $Enable -Force

Set-ItemProperty -Path $WinlogonPath -Name DefaultUserName -Value $Username -Force
Set-ItemProperty -Path $WinlogonPath -Name DefaultDomainName -Value $Domain -Force

if ($AutoLogonCount) {
Set-ItemProperty -Path $WinlogonPath -Name AutoLogonCount -Value $AutoLogonCount -Force
} else {
Remove-ItemProperty -Path $WinlogonPath -Name AutoLogonCount -ErrorAction SilentlyContinue
}

if ($RemoveLegalPrompt) {
Set-ItemProperty -Path $WinlogonPath -Name LegalNoticeCaption -Value $null -Force
Set-ItemProperty -Path $WinlogonPath -Name LegalNoticeText -Value $null -Force

Set-ItemProperty -Path $WinlogonBannerPolicyPath -Name legalnoticecaption -Value $null -Force
Set-ItemProperty -Path $WinlogonBannerPolicyPath -Name legalnoticetext -Value $null -Force
}
} catch {
throw 'Failed to set auto logon. The error was: "{0}".' -f $_
}

}

<#
.SYNOPSIS
Enables auto logon using the specified username and password.

.PARAMETER Username
The username of the user to automatically logon as.

.PARAMETER Password
The password for the user to automatically logon as.

.PARAMETER Domain
The domain of the user to automatically logon as.

.PARAMETER AutoLogonCount
The number of logons that auto logon will be enabled.

.PARAMETER RemoveLegalPrompt
Removes the system banner to ensure interventionless logon.

.PARAMETER BackupFile
If specified the existing settings such as the system banner text will be backed up to the specified file.

.EXAMPLE
PS C:\> Set-SecureAutoLogon `
-Username $env:USERNAME `
-Password (Read-Host -AsSecureString) `
-AutoLogonCount 2 `
-RemoveLegalPrompt `
-BackupFile "C:\WinlogonBackup.xml"

.INPUTS
None.

.OUTPUTS
None.

.NOTES
Revision History:
2011-04-19 : Andy Arismendi - Created.
2011-09-29 : Andy Arismendi - Changed to use LSA secrets to store password securely.

.LINK
http://support.microsoft.com/kb/324737

.LINK
http://msdn.microsoft.com/en-us/library/aa378750

#>