More New Stuff in PowerShell V5: Extra PowerShell Auditing

With the latest Preview release of PowerShell V5 July (X86, X64), we get some extra capabilities for auditing PowerShell script tracing. Since PowerShell V3, we have had the capability of Module Logging in PowerShell, meaning that we can track the commands that are being run for specified PowerShell modules in the event logs. As a refresher, I will show you both of the two ways that this can be enabled and what the expected event logs will look like.

Module Loading

This is the manual approach, but allows you to track the execution of the commands in the PowerShell console. This has to be done for each module that you want this enabled for and is only valid for the current session. You can use either Import-Module or Get-Module and list the module names in parenthesis and specify the LogPipelineExecutionDetails = $True. We can quickly look at the Microsoft.PowerShell modules to see if any of those have logging enabled.

Get-Module Microsoft.* | 
Select Name, LogPipelineExecutionDetails

 

image

None of these are enabled…yet. Time to make that change.

Get-Module Microsoft.* | ForEach {
    $_.LogPipelineExecutionDetails = $True
}

image

All right! Now we are set to run some commands and see what happens in the event logs.

Note: Here is how to do it using Import-Module:

(Import-Module ActiveDirectory).LogPipelineExecutionDetails = $True

After some various commands such as Get-ChildItem, we can look at the event logs to see that all of this has been logged

image

Ok, this is a little hard to see unless you click on the image, but what we are looking at is the Windows PowerShell event log and the Pipeline Execution Details (ID 800) which shows not only the commands that were run, but also the parameters and who actually ran the command!

image

Ok, this is PowerShell so obviously I need to do this the ‘proper way’.

Get-WinEvent -FilterHashtable @{LogName='Windows PowerShell';Id ='800'} -MaxEvents 1 | 
Select -Expand Message

 

image

Errors are also logged as well if something happens.

image

Pretty cool, right? I mentioned that there were two ways to accomplish this and that other way is via Group Policy.

Group Policy

You can either set this via Computer and/or User policy under Administrative Templates/Windows Components/Windows PowerShell under Turn On Module Logging.

Once you enable the policy setting, you can then specify the modules that you wish to track the execution of commands.

image

SNAGHTML14756c46

SNAGHTML147767b8

Once that is done and the GPO has been applied to the systems, you will start seeing the new event logs pop in.

One thing to keep in mind is that the logs may start churning rather hard, so you need to make sure that you are archiving the logs to ensure that you do not miss anything important or have some sort of event forwarding capability to send these logs off to a syslog server.

Latest Script Logging Enhancements

So I have talked about all of this stuff that has been around for a while for a reason. The PowerShell team has improved on this logging by now allowing more detailed logging of scriptblocks to the PowerShell ETW log.

There are a couple of Environmental Variables that need to be configured in order to achieve the enhanced logging. Those are:

  • PSLogScriptBlockExecution
    • When this environment variable exists (its value doesn’t matter), Windows PowerShell logs the text of scripts and script blocks the first time they are seen.
  • PSLogScriptBlockExecutionVerbose
    • When this environment variable exists (its value doesn’t matter), Windows PowerShell logs execution details (begin and end) when logged script blocks are invoked.

Group policy would be the idea approach for this. This is available for both User and Computer policies, so it is up to you which one (or both) that you want to use.

SNAGHTML1486ac4a

These events are not  located in the Windows PowerShell event log, but are located in the Microsoft/Windows/PowerShell/Operational ETW log. The following event IDs correspond to the type of logging you enabled:

  • PSLogScriptBlockExecution
    • Event ID 4104
  • PSLogScriptBlockExecutionVerbose
    • Event ID 4105 (Starting scriptblock) and 4106 (Completing scriptblock)

Note: This does log for things ran through the ISE just like it will for the console.

Now I will run a similar command as I did earlier so we can see what else is being logged.

Get-ChildItem -Path C:\DSC -Filter *.mof -Recurse

Now let’s check out those logs.

image

With that command, we kicked off 36 logs. If you notice, most of the logs are a result of setting PSLogScriptBlockExecutionVerbosewith events 4105 and 4106 tracking each scriptblock creation.

image

image

Event 4014 lists the command being run.

image

Nothing that impressive just yet, but notice that we do not have to specify a specific module to track. Now let’s compile a function on the fly and let it run.

Function Do-Something { 
    Param ($Stuff) 
    Write-Output $Stuff
}

We can see under EventID 4104 that it is compiling the scriptblock.

image

And when we run the fuction:

Do-Something -Stuff 123

image

 

Now I am going to use the example from the July ReadMe file to show an obfuscated command and how it is tracked both in the Windows PowerShell log and the ETW logs.

First the PowerShell log:

Build the function out:

function SuperDecrypt {
    param($script)

    $bytes = [Convert]::FromBase64String($script)
                
    ## XOR “encryption”
    $xorKey = 0x42
    for($counter = 0; $counter -lt $bytes.Length; $counter++)
    {
        $bytes[$counter] = $bytes[$counter] -bxor $xorKey
    }

    [System.Text.Encoding]::Unicode.GetString($bytes)
}

Check the PowerShell log:

image

Well, that’s not off to a good start. Now let’s run the function.

$decrypted = SuperDecrypt "FUIwQitCNkInQm9CCkItQjFCNkJiQmVCEkI1QixCJkJlQg=="
Invoke-Expression $decrypted  

 

image

image

Ok, we got a little bit of info only because I was tracking the modules that use Write-Host and Invoke-Expression. If I didn’t have this enabled, well, this wouldn’t have been easy to catch.

Now let’s try this using the enhanced logging:

Build the function:

image

Here we can see the function being created in the ETW logs. Now let’s run it as well.

image

Oh look, it caught the variable being created as well!

Now for the rest of the command.

image

image

It doesn’t give you the pipeline binding that you would see with the PowerShell event log, but it does give you enough information to know what is happening here.

I wanted to do a little more testing just to see what else was logged. Here is similar code but instead I invoked the code as a scriptblock instead of using Invoke-Expression.

{$decrypted}.Invoke()

image

While this would have never been caught through module logging, it was caught here. Although it didn’t give us the contents of $decrypted, it at least showed that something was being invoked.

Last thing to try out: running an encoded command through PowerShell.exe.

$command = '[console]::WriteLine("Something malicious happening!")'
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)

$EncodedCommand produces this:

WwBjAG8AbgBzAG8AbABlAF0AOgA6AFcAcgBpAHQAZQBMAGkAbgBlACgAIgBTAG8AbQBlAHQAaABpAG4AZwAgAG0AYQBsAGkAYwBpAG8AdQBzACAAaABhAHAAcABlAG4AaQBuAGcAIQAiACkA

 

Now I run PowerShell.exe from the Run menu with this encoded command.

powershell.exe -encodedcommand WwBjAG8AbgBzAG8AbABlAF0AOgA6AFcAcgBpAHQAZQBMAGkAbgBlACgAIgBTAG8AbQBlAHQAaABpAG4AZwAgAG0AYQBsAGkAYwBpAG8AdQBzACAAaABhAHAAcABlAG4AaQBuAGcAIQAiACkA

image

It’s right after EventID 40962 that we see the event that shows the encoded command being created prior to running.

And I do get this in the PowerShell event log only if I have Module Logging enabled through Group Policy.

image

As with the PowerShell event log, this will generate A LOT of events, especially if you enabled PSLogScriptBlockExecutionVerbose as it really pumps out the events. But there you go, a couple more ways to audit PowerShell usage that begin in PowerShell V5 July Preview!

Posted in powershell, V5 | Tagged , , , | 1 Comment

More New Stuff in PowerShell V5: Expand and Compress Archive Cmdlets

With the July release of PowerShell V5 Preview (Download links are 1/3 of the way down on the DSC download page), there are some new things that have been added that are worth talking about.

Here are the direct download links for the July Preview of PowerShell V5:

If you recall, I talked about some of the new things in PowerShell V5 Preview here and here but now I am going to go over one of the new items that have been added to this release:

Archive Cmdlets

Finally we have cmdlets which allow us to create a zip archive to zip and unzip files.

  • Compress-Archive

image

From what we can see with the help file on Compress-Archive, there are 2 parameter sets that are based on Path and LiteralPath parameters. We have an –Update parameter which allows us to add to an existing archive file. We can also set the CompressionLevel (Fastest, NoCompression or Optimal (Default Value)) for the archive.

Let’s give it a quick run to see what happens:

Get-ChildItem | 
Compress-Archive -DestinationPath 'C:\users\boe.prox\Desktop\Archive.zip' -Verbose

image

image

Note: Using just a folder without a full path name causes the script to throw errors

image

It still creates an archive, but it seems to move it to the parent folder above my current path. It probably should just use the current path if none is given.

SNAGHTMLa9e79bf

This feels like a bug to me and should handle the current directory instead of shipping it somewhere else and throwing errors. If you think so to, feel free to vote this up: https://connect.microsoft.com/PowerShell/feedbackdetail/view/954121/v5-july-preview-compress-archive-should-default-to-current-directory-if-fully-qualified-path-given-for-destination-path

If you don’t use the –Update parameter and try to add something to an existing archive, an error will be thrown reminding you to use –Update.

image

Pretty much all there is to that. Now let’s check out the second cmdlet for archives.

  • Expand-Archive

image

Again, we have 2 parameter sets for Path and LiteralPath. We just have to supply the archive file and a destination path for the archive folder. What I am curious to see is if I do not supply a DestinationPath which requires a folder name and if the folder that everything is unzipped to will be the name of the archive.

Expand-Archive .\Archive.zip -DestinationPath $Pwd -Verbose

image

It appears that it will not create a folder based on the archive name and will instead attempt to unzip the file to the existing directory. If the file exists, you will see errors stating to use the –Force parameter, which makes sense.

 

Now lets run this and specify an actual folder name (but one that doesn’t exist).

Expand-Archive .\Archive.zip -DestinationPath 'UnzippedArchive' -Verbose

image

 

Well, it didn’t like that at all. I guess we have to specify an existing folder in order for this to work. At first I wasn’t sure how I liked that, but copy-item and move-item do not auto create directories if they do not exist, so I would expect this cmdlet to do the same.

image

image

Well, that is it for checking out the new cmdlets: Compress-Archive and Expand-Archive! I will look to dive into another new addition to PowerShell V5 July Release in the coming days. Stay tuned!

Posted in powershell | Tagged , , , , , , | 6 Comments

Revisiting Get-FileTimestamp with Reflection

Ever since I wrote my function to locate a file’s MFT (ChangeTime) timestamp, I wanted to take on an extra challenge by taking all of the C# code that included using pinvoke and rewrite it using reflection in PowerShell after seeing fellow PowerShell MVP Matt Graeber’s article on using reflection. While there may not have been a need to keep this particular code resident in memory, it will server as a fun exercise in rewriting all of the C# code and avoid the use of Add-Type to compile the code (which does write to disk).

The plan is to piece through each part of code and show the translation from C# to using reflection to create the Structs, Enums and pinvoke methods in memory by showing the C# code followed by the PowerShell code. If you want to check out the code in its entirety, you can find the majority of it on the link that I referenced at the beginning of this article.

The following code is the initial part of the C# code that defines my environment and new class.  Nothing here will be translated over to PowerShell using reflection.

using System;
using System.Runtime.InteropServices; // for DllImport and friends
using Microsoft.Win32.SafeHandles; // for SafeHandle
using System.Collections.Generic; // for ParseFileAttributes helper function (List<FileAttributes> only
using System.IO; // for test main (FileStream) only
using System.Text; // for test main (Encoding) only
using System.Threading; // for test main (Thread.Sleep) only
using System.Diagnostics; // for test main (Trace.Write[Line]) only
public class Nt
{

First off, I am going to build my dynamic assembly and module which will be used the rest of the time to help define other types that will be required at different points.

#region Module Builder
$Domain = [AppDomain]::CurrentDomain
$DynAssembly = New-Object System.Reflection.AssemblyName('TestAssembly')
# Only run in memory by specifying [System.Reflection.Emit.AssemblyBuilderAccess]::Run
$AssemblyBuilder = $Domain.DefineDynamicAssembly(
    $DynAssembly, 
    [System.Reflection.Emit.AssemblyBuilderAccess]::Run
) 
$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('TimeStampModule', $False)
#endregion Module Builder

What I’ve done here is creating a new assembly called TestAssembly that is used later to build a dynamic module that is used to build out our required components (structs, enums, pinvoke, etc…).

I then hook into the current AppDomain and use a method called DefineAssembly() to configure the custom assembly (stored in memory by specifying [System.Reflection.Emit.AssemblyBuilderAccess]::Run). Once that is done, I build my dynamic module using DefineDynamicModule() and specify a module name and and whether the symbol information should be emitted.

Up next is where I define a Struct for IO_STATUS_BLOCK which is used to determine the status of using the win32 function,  NTQueryInformationFile.

struct IO_STATUS_BLOCK
{
    internal uint status;
    internal ulong information;
}

And now for the PowerShell reflection approach:

#region STRUCTs

#region IOStatusBlock
#Define STRUCT
$Attributes = 'AutoLayout, AnsiClass, Class, Public, SequentialLayout, Sealed, BeforeFieldInit'
$TypeBuilder = $ModuleBuilder.DefineType('IOStatusBlock', $Attributes, [System.ValueType], 1, 0x40)
[void]$TypeBuilder.DefineField('status', [UInt64], 'Public')
[void]$TypeBuilder.DefineField('information', [UInt64], 'Public')

#Create STRUCT Type
[void]$TypeBuilder.CreateType()
#endregion IOStatusBlock

 

I use my $ModuleBuilder that I defined earlier to build a new Struct using the DefineType() method in which I specify a name for the Struct as well as specifying some attributes to make it more like a Struct (I found out what these attributes were by looking at my previously compiled C# code and using Get-Member against the Struct). From there I take the Type I have created and use the DefineField() method to build out the fields to match what is required of the IO_STATUS_BLOCK. The Define() method requires that I supply a name, a type and the FieldAttribute of the field. Once completed, I simply call CreateType() to complete the build of the type so it is available to me.

image

The next Struct is for FILE_BASIC_INFORMATION and is a little different to build out due to some unique requirements:

[StructLayout(LayoutKind.Explicit)]
struct FILE_BASIC_INFORMATION
{
    [FieldOffset(0)]
    internal long CreationTime;
    [FieldOffset(8)]
    internal long LastAccessTime;
    [FieldOffset(16)]
    internal long LastWriteTime;
    [FieldOffset(24)]
    internal long ChangeTime;
    [FieldOffset(32)]
    internal ulong FileAttributes;
}

Here I have a StructLayout attribute as well as FieldOffsets which define the physical position of the field in the struct. Have on fear though, we can easily put this one together!

#region FileBasicInformation
#Define STRUCT
$Attributes = 'AutoLayout, AnsiClass, Class, ExplicitLayout, Sealed, BeforeFieldInit,public'
$TypeBuilder = $ModuleBuilder.DefineType('FileBasicInformation', $Attributes, [System.ValueType], 8, 0x40)
$CreateTimeField = $TypeBuilder.DefineField('CreationTime', [UInt64], 'Public')
$CreateTimeField.SetOffset(0)
$LastAccessTimeField = $TypeBuilder.DefineField('LastAccessTime', [UInt64], 'Public')
$LastAccessTimeField.SetOffset(8)
$LastWriteTimeField = $TypeBuilder.DefineField('LastWriteTime', [UInt64], 'Public')
$LastWriteTimeField.SetOffset(16)
$ChangeTimeField = $TypeBuilder.DefineField('ChangeTime', [UInt64], 'Public')
$ChangeTimeField.SetOffset(24)
$FileAttributesField = $TypeBuilder.DefineField('FileAttributes', [UInt64], 'Public')
$FileAttributesField.SetOffset(32)

#Create STRUCT Type
[void]$TypeBuilder.CreateType()
#endregion FileBasicInformation

 

Same as before, I use my ModuleBuilder to define the type with specific attributes (I have added ExplicitLayout to match the [StructLayout(LayoutKind.Explicit)] attribute defined in C#) and then start adding the fields. The thing you might notice is that I save each field from DefineField() to a variable. This is because I must then call the SetOffset() method and supply an integer which will define the physical position of the field. This is how I do my FieldOffset!

image

That does it for the Structs! Time to do an Enum.

I only have one Enum to do for FILE_INFORMATION_CLASS. I didn’t need everything, just the following items that are listed below in the C# code.

enum FILE_INFORMATION_CLASS
{
    FileDirectoryInformation = 1,        // 1
    FileBasicInformation = 4,            // 4
    FileHardLinkInformation = 46        // 46   
}

The refactored code for PowerShell is below:

#region ENUMs
$EnumBuilder = $ModuleBuilder.DefineEnum('FileInformationClass', 'Public', [UInt32])
# Define values of the enum
[void]$EnumBuilder.DefineLiteral('FileDirectoryInformation', [UInt32] 1)
[void]$EnumBuilder.DefineLiteral('FileBasicInformation', [UInt32] 4)
[void]$EnumBuilder.DefineLiteral('FileModeInformation', [UInt32] 16)
[void]$EnumBuilder.DefineLiteral('FileHardLinkInformation', [UInt32] 46)

#Create ENUM Type
[void]$EnumBuilder.CreateType()
#endregion ENUMs

Unlike the previous items, I will be calling DefineEnum() vs. DefineType() and supplying the name of the enum, the type attributes and underlying type for the enum. From there I start adding the enum values using DefineLiteral() and giving values for the name of the value name, the underlying type (must be UInt32 because UInt16 will work on PowerShell V3, but V4+ will fail when using the pinvoke method) and the numeric value of the name. Once that has completed, I call the familiar CreateType() method and away we go!

image

Now to hook into the Win32 API using pinvoke and grabbing the NTQueryInformationFile() function.

[DllImport("ntdll.dll", SetLastError = true)]
    static extern IntPtr NtQueryInformationFile(SafeFileHandle fileHandle, 
    ref IO_STATUS_BLOCK IoStatusBlock, 
    IntPtr pInfoBlock, 
    uint length, 
    FILE_INFORMATION_CLASS fileInformation);

 

The PowerShell code is somewhat larger as we have to build everything out, but it is by no means impossible. I will step through by splitting up some parts of the code to better explain what is happening here.

#region DllImport
$TypeBuilder = $ModuleBuilder.DefineType('ntdll', 'Public, Class')

#region NtQueryInformationFile Method
$PInvokeMethod = $TypeBuilder.DefineMethod(
    'NtQueryInformationFile', #Method Name
    [Reflection.MethodAttributes] 'PrivateScope, Public, Static, HideBySig, PinvokeImpl', #Method Attributes
    [IntPtr], #Method Return Type
    [Type[]] @([Microsoft.Win32.SafeHandles.SafeFileHandle], 
    [IOStatusBlock], [IntPtr] ,[UInt64], [FileInformationClass]) #Method Parameters
)

Once again, I use my ModuleBuilder object to define my type. This time I am specifying a name of the dll that the function resides on (I don’t have to do this, but it makes sense to make it something that relates to what I am using) as well as specifying the type attributes.

From there I can begin to define my NTQueryInformationFile method (the win32 function). To do this I use the DefineMethod() method and supply the name of the function, the method attributes (I found this by reviewing the method attributes on the compiled C# code), the return type that the function provides when used (in this case an IntPtr) and lastly I supply the required parameter types that are required for this to actually work. Fortunately, I can get all of this information from the MSDN page. This is a array of Types that consist of a SafeFileHandle, the IOStatusBlock type, an IntPtr, an UInt64 and lastly the FileInformationClass type.

$DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
$FieldArray = [Reflection.FieldInfo[]] @(
    [Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
    [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')
)

$FieldValueArray = [Object[]] @(
    'NtQueryInformationFile', #CASE SENSITIVE!!
    $True
)

$SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder(
    $DllImportConstructor,
    @('ntdll.dll'),
    $FieldArray,
    $FieldValueArray
)

 

This piece is where I build out and define the attributes of the DLLImport attribute. In this case, I only need to worry about setting the EntryPoint (which is the actual Win32 function that this will use – AND IT IS CASE SENSITIVE) and the SetLastError field that I can use to get any error messages if this happens to fail.

$PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)
#endregion NtQueryInformationFile Method

[void]$TypeBuilder.CreateType()
#endregion DllImport

 

I add that custom attribute to my method and then create the type.

image

Now I have a working method that will call a Win32 function! But that is not all! I had some extra stuff coded in C# (that really wasn’t needed, but I did it in C# just to learn more about it) that needs to be moved to PowerShell.

public static bool GetFourFileTimes(string path2file,
        out DateTime creationTime, out DateTime lastAccessTime, 
        out DateTime lastWriteTime, out DateTime changeTime, out string errMsg)
{
    bool brc = false;
    creationTime = default(DateTime);
    lastAccessTime = default(DateTime);
    lastWriteTime = default(DateTime);
    changeTime = default(DateTime);
    errMsg = string.Empty;
    IntPtr p_fbi = IntPtr.Zero;
    try
    {
        FILE_BASIC_INFORMATION fbi = new FILE_BASIC_INFORMATION();
        IO_STATUS_BLOCK iosb = new IO_STATUS_BLOCK();
        using (FileStream fs = new FileStream(path2file, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            p_fbi = Marshal.AllocHGlobal(Marshal.SizeOf(fbi));               
            IntPtr iprc = NtQueryInformationFile(fs.SafeFileHandle, ref iosb, p_fbi, 
            (uint)Marshal.SizeOf(fbi), FILE_INFORMATION_CLASS.FileBasicInformation);
            brc = iprc == IntPtr.Zero && iosb.status == 0;
            if (brc)
            {
                brc = false;
                fbi = (FILE_BASIC_INFORMATION)Marshal.PtrToStructure(p_fbi, typeof(FILE_BASIC_INFORMATION));
                creationTime = DateTime.FromFileTime(fbi.CreationTime);
                lastAccessTime = DateTime.FromFileTime(fbi.LastAccessTime);
                lastWriteTime = DateTime.FromFileTime(fbi.LastWriteTime);
                changeTime = DateTime.FromFileTime(fbi.ChangeTime);
                brc = true;
            }
        }
    }
    catch (Exception ex)
    {
        brc = false;
        errMsg = ex.Message;
    }
    finally
    {
        if (p_fbi != IntPtr.Zero) { Marshal.FreeHGlobal(p_fbi); }
    }
    return brc;
}

 

Pay no attention to the GetFourFileTimes() method as it will not be brought over into my PowerShell code. As you can see, there are a few things happening here that I need to migrate but nothing that is incredibly complex.

$fbi = New-Object "FileBasicInformation"
$iosb = New-Object "IOStatusBlock"

$FileStream = [System.IO.File]::Open("C:\Users\proxb\desktop\desktop.ini",'Open','Read','ReadWrite')

$p_fbi = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Runtime.InteropServices.Marshal]::SizeOf($fbi))

First I create new objects using my newly created Structs. I then open up a file stream to a file of my choosing and making sure that others could access it if needed by supply values for the parameters of the Open() method of the System.Io.File type that ensure the file is not being locked.

I then get the physical location in memory ($p_fbi using AllocHGlobal()) of the FileBasicInformation struct type by first getting its size in bytes using the SizeOf() method (in this case, the size is 64 bytes).

$iprc = [ntdll]::NtQueryInformationFile($FileStream.SafeFileHandle, $iosb, $p_fbi, 
    [System.Runtime.InteropServices.Marshal]::SizeOf($fbi), [FileInformationClass]::FileBasicInformation
)

Here is where I call my NtQueryInformationFile() method with the required parameter values. I supply my SafeFileHandle from the file stream, my IO_STATUS_BLOCK struct that will be updated for review to see if something went wrong, the physical location of the FILE_BASIC_INFORMATION struct type that will also be updated by the method so I can view the timestamps, the size in bytes of the FILE_BASIC_INFORMATION type and finally the FILE_INFORMATION_CLASS type with FileBasicInformation. The returned IntPtr value will be used later on to determine if this was successful.

 

If ($IsOK) {
    # Pull data from unmanaged memory block into a usable object
    $fbi = [System.Runtime.InteropServices.Marshal]::PtrToStructure($p_fbi, [FileBasicInformation])
    [pscustomobject]@{
        FullName = $FileStream.Name
        CreationTime = [datetime]::FromFileTime($fbi.CreationTime)
        LastAccessTime = [datetime]::FromFileTime($fbi.LastAccessTime)
        LastWriteTime = [datetime]::FromFileTime($fbi.LastWriteTime)
        ChangeTime = [datetime]::FromFileTime($fbi.ChangeTime)
    }
} Else {
    Write-Warning "$($Item): $(New-Object ComponentModel.Win32Exception)"
}

 

After running the method, I now determine if everything went according to plan. If not an error will be thrown, otherwise I will continue on my path by marshaling the data ($p_fbi using PtrToStructure()) from the unmanaged memory over to a managed object that I can then look at (my $fbi type I created earlier). Now it is just a matter of converting the timestamps to a more human readable value using the FromFileTime() method and then displaying the results.

image

So there you go! I have tossed out my C# here string that was compiled using Add-Type and instead took the dive into Reflection to accomplish the same thing. While this is something that a very small percentage of people might ever use, it was absolutely a fun time learning more about this!

By the way, I did update my Get-FileTimeStamp function to use Reflection instead of compiled C#. You can download the updated function from the link below.

http://gallery.technet.microsoft.com/scriptcenter/Get-MFT-Timestamp-of-a-file-9227f399

Posted in powershell | Tagged , , , , , , | 1 Comment

PowerShell and WPF: Radio Button

I talked about working with CheckBoxes in my previous article and mentioned briefly how you can have them behave like a Radio button, but in the end, nothing beats the actual thing no matter how close you can make another control behave. A Radio button only allows a single selection for its specific group. When you select something else, the current item will be unchecked while the new item is checked and there are only two options unlike a checkbox: Checked and Unchecked.

#Build the GUI
[xml]$xaml = @"
<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> 
    <StackPanel > 
        <RadioButton x:Name="Item1" Content = 'Item1'/>
        <RadioButton x:Name="Item2" Content = 'Item2'/>
        <RadioButton x:Name="Item3" Content = 'Item3'/>  
        <TextBox />      
    </StackPanel>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )


$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
    Set-Variable -Name ($_.Name) -Value $Window.FindName($_.Name)
}

$Window.Showdialog() | Out-Null

 

image

Nothing really special here. I use <RadioButton> in my XAML code to create the control. By default, every radio button is part of a default group which means that all radio buttons will follow the same suit of being unchecked while a single button is checked. There is a property that you can use called GroupName which can be used to (as the name implies) group radio buttons to handle different parts and lets you have multiple selections with radio buttons.

#Build the GUI
[xml]$xaml = @"
<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> 
    <StackPanel > 
        <RadioButton x:Name="Item1" Content = 'Item1(Group1)' GroupName='Group1'/>
        <RadioButton x:Name="Item2" Content = 'Item2(Group1)' GroupName='Group1'/>
        <RadioButton x:Name="Item3" Content = 'Item3(Group1)' GroupName='Group1'/>  
        <Separator/>
        <RadioButton x:Name="Item4" Content = 'Item4(Group2)' GroupName='Group2'/>  
        <RadioButton x:Name="Item5" Content = 'Item5(Group2)' GroupName='Group2'/>  
        <RadioButton x:Name="Item6" Content = 'Item6(Group2)' GroupName='Group2'/>  
        <TextBox />      
    </StackPanel>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
    Set-Variable -Name ($_.Name) -Value $Window.FindName($_.Name)
}

$Window.Showdialog() | Out-Null

image

Anything in Group1 is bound by the limitations of the radio button and the same goes for the items in Group2. Only one item in each group can be checked, but because I am using GroupNames, I can have one of each checked in their respective groups.

If you are not a fan of the default foreground and background colors, we can change those up as well using the Background and Foreground properties in the XAML code.

#Build the GUI
[xml]$xaml = @"
<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> 
    <StackPanel x:Name='StackPanel'> 
        <RadioButton x:Name="Item1" Content = 'Item1(Group1)' GroupName='Group1' Background='Black' Foreground='Red'/>
        <RadioButton x:Name="Item2" Content = 'Item2(Group1)' GroupName='Group1' Background='Black' Foreground='Red'/>
        <RadioButton x:Name="Item3" Content = 'Item3(Group1)' GroupName='Group1' Background='Black' Foreground='Red'/>  
        <Separator/>
        <RadioButton x:Name="Item4" Content = 'Item4(Group2)' GroupName='Group2' Background='Yellow' Foreground='Blue'/>  
        <RadioButton x:Name="Item5" Content = 'Item5(Group2)' GroupName='Group2' Background='Yellow' Foreground='Blue'/>  
        <RadioButton x:Name="Item6" Content = 'Item6(Group2)' GroupName='Group2' Background='Yellow' Foreground='Blue'/>  
        <TextBox />      
    </StackPanel>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
    Set-Variable -Name ($_.Name) -Value $Window.FindName($_.Name)
}

$Window.Showdialog() | Out-Null

image

The background property defines the background color of the radio button itself while the foreground property defines the text and the radio button when it has been checked.

Much like a Checkbox, we can find out which button is checked using the IsChecked property.

$StackPanel.Children | Where {
    $_ -is [system.windows.controls.radiobutton] -and $_.IsChecked
} | Select Name

 

image

We can also handle events much like the checkboxes on the Checked event.

#Build the GUI
[xml]$xaml = @"
<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> 
    <StackPanel x:Name='StackPanel'> 
        <RadioButton x:Name="Item1" Content = 'Item1'/>
        <RadioButton x:Name="Item2" Content = 'Item2'/>
        <RadioButton x:Name="Item3" Content = 'Item3'/>  
        <Separator/>
        <TextBox x:Name='textbox'/>      
    </StackPanel>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

$xaml.SelectNodes("//*[@*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach {
    Set-Variable -Name ($_.Name) -Value $Window.FindName($_.Name) -Scope Script
}

#Bubble up event handler
[System.Windows.RoutedEventHandler]$Script:CheckedEventHandler = {
    $TextBox.Text = $_.source.name
}
$StackPanel.AddHandler([System.Windows.Controls.RadioButton]::CheckedEvent, $CheckedEventHandler)

$Window.Showdialog() | Out-Null

 

image

The magic here happens with the following code:

#Bubble up event handler
[System.Windows.RoutedEventHandler]$Script:CheckedEventHandler = {
    $TextBox.Text = $_.source.name
}
$StackPanel.AddHandler([System.Windows.Controls.RadioButton]::CheckedEvent, $CheckedEventHandler)

 

I a creating a routed event handler which is basically a script block that be used when a specified event occurs. In this case, the scriptblock just takes the object returned from the event (System.Windows.RoutedEventArgs).

image

The scriptblock then uses the Source property which has the control object (radio button) that we can then use to update the textbox. I set the event handler on the StackPanel which allows me to not have to worry about setting the event handler on each radio button.

That is all to working with Radio buttons in WPF using PowerShell. If you have any questions or other things that you would like to see, feel free to leave a comment!

Posted in powershell, WPF | Tagged , , , | 1 Comment

PowerShell and WPF: Checkboxes

Continuing on with my series on PowerShell and WPF, I am going to talk about using checkboxes in a form.

Checkboxes are a great way to allow a user to select a specific item or items prior to moving on with an action.

An example of this is below:

#Build the GUI
[xml]$xaml = @"
<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> 
    <StackPanel > 
        <CheckBox x:Name="Item1" Content = 'Item1'/>
        <CheckBox x:Name="Item2" Content = 'Item2'/>
        <CheckBox x:Name="Item3" Content = 'Item3'/>  
        <TextBox />      
    </StackPanel>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

$Item1 = $Window.FindName('Item1')
$Item2 = $Window.FindName('Item2')
$Item3 = $Window.FindName('Item3')

$Window.Showdialog() | Out-Null

image

With this approach, we can check 1 or more of these checkboxes.

image

With these items checked, we can check the IsChecked property for each checkbox to see that they have been checked which is great when using a comparison operator such as –eq or –ne.

image

So far, we have seen how there are two states: checked and unchecked. But there is also another state that you can use which requires you to set the property IsThreeState = ‘True’ on the checkbox control. By doing so, you now get something like this:

image

What do you think will happen when we look at the IsChecked properties of each checkbox, especially $Item2.

image

In this case, the checkbox with the alternate checkbox doesn’t have a True or False property, it is simply Null. Something to keep in mind when working with the IsThreeState property.

I have covered a couple of the more useful properties, now it is time to look at a few events that relate to each of the types of checkbox IsChecked properties.

Using the Checked event, whenever the checkbox is set to the IsChecked=$True property, an event can kick off. Same goes for the Unchecked event with IsProperty=$False. Indeterminate event only works when the checkbox is not a checkbox and not unchecked.

I’ve added a couple more textboxes to handle each checkbox.

#Build the GUI
[xml]$xaml = @"
<Window 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window" Title="Initial Window" WindowStartupLocation = "CenterScreen"
    SizeToContent = "WidthAndHeight" ShowInTaskbar = "True" Background = "lightgray"> 
    <StackPanel >
        <CheckBox x:Name="Item1" Content = 'Item1' IsThreeState = 'True' />
        <CheckBox x:Name="Item2" Content = 'Item2' IsThreeState = 'True' />
        <CheckBox x:Name="Item3" Content = 'Item3' IsThreeState = 'True' />  
        <TextBox x:Name='Item1_txt' />      
        <TextBox x:Name='Item2_txt' />      
        <TextBox x:Name='Item3_txt' />      
    </StackPanel>
</Window>
"@
 
$reader=(New-Object System.Xml.XmlNodeReader $xaml)

#Connect to Controls
$Window=[Windows.Markup.XamlReader]::Load( $reader )

$Item1 = $Window.FindName('Item1')
$Item2 = $Window.FindName('Item2')
$Item3 = $Window.FindName('Item3')
$Item1_txt = $Window.FindName('Item1_txt')
$Item2_txt = $Window.FindName('Item2_txt')
$Item3_txt = $Window.FindName('Item3_txt')

#Events
## Item1
$Item1.Add_Checked({
    $Item1_txt.Text = "$($This.name) State: Checked"
})
$Item1.Add_UnChecked({
    $Item1_txt.Text = "$($This.name) State: Unchecked"
})
$Item1.Add_Indeterminate({
    $Item1_txt.Text = "$($This.name) State: Indeterminate"
})

## Item2
$Item2.Add_Checked({
    $Item2_txt.Text = "$($This.name) State: Checked"
})
$Item2.Add_UnChecked({
    $Item2_txt.Text = "$($This.name) State: Unchecked"
})
$Item2.Add_Indeterminate({
    $Item2_txt.Text = "$($This.name) State: Indeterminate"
})

## Item3
$Item3.Add_Checked({
    $Item3_txt.Text = "$($This.name) State: Checked"
})
$Item3.Add_UnChecked({
    $Item3_txt.Text = "$($This.name) State: Unchecked"
})
$Item3.Add_Indeterminate({
    $Item3_txt.Text = "$($This.name) State: Indeterminate"
})


$Window.Showdialog() | Out-Null

 

image

If you are looking to have a group of checkboxes in which only one can be selected in the group, then you have 2 options:

  • Create an event handler to manually uncheck the other checkboxes when a checkbox is checked
  • Use the RadioButton control instead in which this is the controls purpose.

I won’t cover the RadioButton here, but will show a quick example of using a event to to uncheck other checkboxes when one has been checked.

I removed the IsThreeState property from this round and will only focus on the two possible options. Rather than make an event for each checkbox, I am going to make a single event scriptblock and apply it to each control.

#Events
## Checked Events
[System.Windows.RoutedEventHandler]$Script:CheckBoxChecked = {
    $Window.Content.Children | Where {
        $_ -is [System.Windows.Controls.CheckBox] -AND $This.Name  -ne $_.Name
    } | ForEach {
        $_.IsChecked = $False
    }
}
$Window.Content.Children | Where {
    $_ -is [System.Windows.Controls.CheckBox]
} | ForEach {
    $_.AddHandler([System.Windows.Controls.CheckBox]::CheckedEvent, $CheckBoxChecked)
}

Here I define the scriptblock to look for all checkboxes which do not match the name of the checkbox that had been clicked to show a checkbox. From there, I go through each checkbox and set the IsChecked property to $False. After applying each of these event scriptblocks to all of my Checkbox controls, we are set to try and click each checkbox to see if I can have more than one at a time.

image

That is all to working with the Checkbox control using PowerShell and WPF!

Posted in powershell, WPF | Tagged , , | 1 Comment