• Returning Rich Objects from Functions (Part 1)

    If a PowerShell function needs to return more than one information kind, always make sure you wrap the pieces of information inside a rich object. The easiest way to produce such a custom object is [PSCustomObject]@{} like this:

    function Get-TestData 
    {
      # if a function is to return more than one information kind,
      # wrap it in a custom object
    
      [PSCustomObject]@{
          # wrap anything you'd like to return
       
    • 9 Jul 2018
  • Using persisting variables inside functions

    By default, when a PowerShell function exits, it “forgets” all internal variables. However, there is a workaround that creates persisting internal variables. Here is how:

    # create a script block with internal variables
    # that will persist
    $c = & { 
        # define an internal variable that will 
        # PERSIST and keep its value even though
        # the function exits
        $a = 0
    
        {
            # use the internal…
    • 6 Jul 2018
  • Using Default Parameters

    If you find yourself always using the same parameter values over again, try using PowerShell default parameters. Here is how:

    # hash table
    # Key =
    # Cmdlet:Parameter
    # Value = 
    # Default value for parameter
    # * (Wildcard) can be used
    
    $PSDefaultParameterValues = @{ 
    'Stop-Process:ErrorAction' = 'SilentlyContinue' 
    '*:ComputerName' = 'DC-01'
    'Get-*:Path' = 'c:\windows'
    }
    • 5 Jul 2018
  • Speed Difference: Reading Large Log Files

    When it comes to reading large log files and, for example, extracting error messages, PowerShell can either use the low memory pipeline, or the high memory classical loops. The difference is not just the memory consumption, though, but also the speed.

    With the pipeline, you do not need much memory but the script may also be very slow. By using the classic loop, the script produces the same results 10x to 100x faster:

    • 4 Jul 2018
  • Finding Executable for File

    Most things can be handled by built-in PowerShell commands, but if that’s not enough, you can always resort to the internal Windows API. For example, if you’d like to find out what the application is that is associated with a given file, try this:

    function Get-ExecutableForFile
    {
        param
        (
            [Parameter(Mandatory)]
            [string]
            $Path
        )
    
        $Source = @"
    
    using System;
    using System…
    • 3 Jul 2018
  • Understanding Script Block Logging (Part 7)

    This is part 7 of our mini-series covering PowerShell script block logging. We now just need some cleanup tool that can clear the script block logging log. For this, you need Administrator privileges.

    Before you clear the log: this will clear the entire PowerShell log. If you do not own the machine, make sure it is OK to delete this information. It may be used by others for forensic security analysis.

    Here is a function…

    • 2 Jul 2018
  • Understanding Script Block Logging (Part 6)

    This is part 6 of our mini-series covering PowerShell script block logging, and it’s time to address a final thing: when you execute very large PowerShell scripts, they are logged in chunks (parts). What’s missing is some logic that defragments the code parts:

    function Get-LoggedCode
    {
      # to speed up SID-to-user translation,
      # we use a hash table with already translated SIDs
      # it is empty at first
      
    • 29 Jun 2018
  • Understanding Script Block Logging (Part 5)

    This is part 5 of our mini-series covering PowerShell script block logging. We are almost done, and what’s missing is a better way to read logged code. In our previous approach, the user who executed the code was returned as a cryptic SID instead of a clear-text name. Here’s a function that turns the user SID into a real name, and also uses a clever cache to speed up the SID lookup:

    function Get-LoggedCod…
    • 28 Jun 2018
  • Understanding Script Block Logging (Part 4)

    This is part 4 of our mini-series covering PowerShell script block logging. By now, you know how to read logged PowerShell code, and how to turn on verbose mode. With verbose mode turned on, any PowerShell code that executes on your machine is logged, so this may produce a lot of data. In order to not overwrite older log entries, you should enlarge the log file. Here is how:

    function Set-SBLLogSize
    {
      <#
          .S…
    • 27 Jun 2018
  • Understanding Script Block Logging (Part 3)

    This is part 3 of our mini-series covering PowerShell script block logging. By default, PowerShell logs only code that is considered security relevant. Today, we’ll enable verbose logging. With verbose logging turned on, any PowerShell code executed on your machine by any user will be logged.

    To enable verbose mode, you need Administrator privileges. Here is a function that enables verbose logging:

    function Enable…
    • 26 Jun 2018
  • Understanding Script Block Logging (Part 2)

    This is part 2 of our mini-series covering PowerShell script block logging. Today, we’ll again read the script block logging log, but this time we’ll try and get back the log data in a more useful object-oriented way:

    function Get-LoggedCode
    {
        # read all raw events
        $logInfo = @{ ProviderName="Microsoft-Windows-PowerShell"; Id = 4104 }
        Get-WinEvent -FilterHashtable $logInfo | 
           
    • 25 Jun 2018
  • Understanding Script Block Logging (Part 1)

    Beginning with PowerShell 5, the PowerShell engine starts to log executed commands and scripts. By default, only commands considered potentially harmful are logged. When you enable verbose logging, though, all executed code from all users on a given machine are logged.

    This is the first part of a mini series introducing you to script block logging. Today, we just want to get to the logged script code in the most basic…

    • 22 Jun 2018
  • Adding Leading Zeroes

    If you need numbers with leading zeroes, for example for server names, here are two approaches. First, you can turn the number into a string, then use PadLeft() to “pad” the string to the desired length:

    $number = 76
    $leadingZeroes = 8
    
    $number.Tostring().PadLeft($leadingZeroes, '0') 
    
    Or, you can use the -f operator:
    
    
    $number = 76
    $leadingZeroes = 8
    
    "{0:d$leadingZeroes}" -f $number
    

    Twitter This Tip!

    • 21 Jun 2018
  • Displaying Message Box

    If you’d like to show a default MessageBox with some buttons for the user to click, try this function:

    function Show-MessageBox
    {
      [CmdletBinding()]
      param
      (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]
        $Text,
        
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]
        $Caption,
        
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [Windows.Mess…
    • 20 Jun 2018
  • Displaying Input Box

    If you’d like to open a quick and dirty input box to prompt a user for some data, you could access Microsoft Visual Basic and “borrow” its InputBox:

    Add-Type -AssemblyName Microsoft.VisualBasic
    $result = [Microsoft.VisualBasic.Interaction]::InputBox("Enter your Name", "Name", $env:username)
    $result
    

    Note though that this approach has some limitations: the input box may open behind…

    • 19 Jun 2018
  • Reading Text Files Fast

    There are plenty of ways how PowerShell can read in text files, and they can differ considerably in time. Check for yourself. The examples below illustrate different approaches and measure the execution times. Just make sure the path in the example exists, and if not, choose a large text file to play with.

    # make sure this file exists, or else
    # pick a different text file that is
    # very large
    $path = 'C:\Windows\Logs…
    • 18 Jun 2018
  • Create Universal Time from Local Time in ISO Format

    If you’re working across countries and time zones, you may want to use universal time instead of local time. And to ensure that the time format is culture neutral, using the ISO format is recommended. Here is how:

    $date = Get-Date
    $date.ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss')
    

    Twitter This Tip! ReTweet this Tip!

    • 15 Jun 2018
  • Reading Event Logs Smart (Part 2)

    In the previous tip we illustrated how you can access detailed event log information that you retrieved via Get-EventLog by using ReplacementStrings. That worked beautifully, however Get-EventLog can only read the “classic” Windows logs. There are hundreds of additional logs in modern Windows versions.

    These logs can be read via Get-WinEvent, and there is a wealth of information to discover. For example, to…

    • 14 Jun 2018
  • Reading Event Logs Smart (Part 1)

    When you query an event log with PowerShell, by default you get back a text message with the logged information. For example, if you’d like to know who logged on to your machine, you could use code like this (Administrator privileges required):

    Get-EventLog -LogName Security -InstanceId 4624 |
      Select-Object -Property TimeGenerated, Message
    

    The result could look like this:

     
    ...
    25.04.2018 07:48:41 An account…
    • 13 Jun 2018
  • Understanding Type Accelerators (Part 2)

    PowerShell comes with a number of hard-coded type accelerators that serve like aliases for commonly used .NET types, and since they are a lot shorter than the original type names, they “accelerate the typing”.

    A little-known fact is that the list of type accelerators is extensible. The line below adds a new type accelerator named “SuperArray” that points to “System.Collections.ArrayList”…

    • 12 Jun 2018
  • Understanding Type Accelerators (Part 1)

    “Type Accelerators” work like aliases for .NET types. They are intended to save typing. For example, the [ADSI] “type” really does not exist. It is a mere alias for System.DirectoryServices.DirectoryEntry. You could replace [ADSI] by [System.DirectoryServices.DirectoryEntry]:

     
    PS> [ADSI].FullName
    System.DirectoryServices.DirectoryEntry 
    
    
    PS> [System.DirectoryServices.DirectoryEntry].FullName…
    • 11 Jun 2018
  • Out-Notepad: Send Information to Notepad

    Have you ever wanted to send text directly to Notepad, without having to use a file?

    Typically, you would need to write the text to a file, then open Notepad and instruct it to load the file. There is also a more exotic way: communicate with Notepad via Windows messages, and beam text right into Notepad. This is what Out-Notepad does:

    #requires -Version 2
    function Out-Notepad
    {
      param
      (
        [Parameter(Mandatory=$t…
    • 8 Jun 2018
  • Using Secret $FormatEnumerationLimit variable

    Format-List by default displays object properties in a list, and if a property contains an array, the array is turned into text, and only a few array elements are displayed. Here is an example:

     
    PS> Get-Process -Id $Pid | Format-List -Property Name, Modules
    
    
    Name    : powershell_ise
    Modules : {System.Diagnostics.ProcessModule (PowerShell_ISE.exe), 
              System.Diagnostics.ProcessModule (ntdll.dll), System.Diagnostics…
    • 7 Jun 2018
  • Turning Display Off Immediately

    If you are about to launch a lengthy automation script, why not turn off the display right away instead of waiting for the screen saver timeout to kick in?

    Here is a simple function that turns off your display immediately. Just move the mouse or press a key to turn it back on:

    function Set-DisplayOff
    {
        $code = @"
    using System;
    using System.Runtime.InteropServices;
    public class API
    {
      [DllImport("user32.dll")]
      public…
    • 6 Jun 2018
  • Configuring Network Adapter

    Here is a simple example illustrating how you assign IP address, gateway, and DNS server to a network adapter. The script lists all active network adapters, and when you select one and click OK, uses the hard-coded addresses to assign new values.

    Note that the script below only pretends to do the changes. Remove the -WhatIf parameter to actually assign new values:

    $NewIP = '192.168.2.12'
    $NewGateway = '192…
    • 5 Jun 2018