• Take Advantage of Your Profile

    When PowerShell launches, it automatically looks for a special autostart script. It does not exist by default and is different for each PowerShell environment. Its path is revealed in $profile. This is what the path looks for my machine, and while inside a Windows PowerShell console:


    You can easily check whether the file exists, and if not…

  • Beware of -match

    The -match operator is frequently used in scripts however not everyone seems to understand how it really works. It can be a really dangerous filter operator.

    Let’s first create some sample data:

    $list = 'ServerName, Location, Status
    Test1, Hannover, Up
    Test2, New York, Up
    Test11, Sydney, Up' | ConvertFrom-Csv 

    The result is a list of hypothetical servers:

    ServerName Location Status
    ---------- …
  • Avoid Add-Member (Part 3)

    In the previous tip we looked at a number of clever alternatives to avoid Add-Member when creating your own new objects.

    While using hash tables to design new objects (as shown in previous tips) is common place, PowerShell 5 and better comes with a much better approach: classes. They can be complex but if you want to use them to create simple data objects, classes are really easy to use.

    This is what you find most often…

  • Avoid Add-Member (Part 2)

    In the previous tip we looked at creating simple data objects, and it became evident that instead of using Add-Member, you can cast a hash table to [PSCustomObject].

    However, what if you need to add a property to an object at a later time? Well, if you already have a real object and want to expand its members, then Add-Member is again the correct way:

    # create a real object:
    $user = [PSCustomObject]@{
        LastName  =
  • Avoid Add-Member (Part 1)

    Often the cmdlet Add-Member is used to create simple objects like this:

    $user = New-Object -TypeName psobject |
        Add-Member -MemberType NoteProperty -Name LastName -Value 'Weltner' -PassThru |
        Add-Member -MemberType NoteProperty -Name FirstName -Value 'Tobias' -PassThru |
        Add-Member -MemberType NoteProperty -Name Id -Value 123 -PassThru 

    This does work, and the result looks like this…

  • Important Keyboard Shortcuts

    Two of the most important keyboard shortcuts in any PowerShell environment – whether console, ISE, or VSCode – are TAB and CTRL+SPACE.

    TAB triggers autocompletion, and as a rule of thumb, you should use this after typing at least 3 characters of a command or parameter name. Press TAB multiple times until autocompletion suggests what you were intending to type. Should autocomplete not work, make sure there…

  • Sending Email

    PowerShell comes with the cmdlet Send-MailMessage so you can easily send off emails and even include attachments. All you need is the name of an SMTP server that accepts your request and does the mail transport, and your authentication credentials.

    However, there are three important caveats these days when using this cmdlet:

    • PowerShell 7 claims the cmdlet is deprecated, and while that is true, there is no better replacement…
  • Using Sysinternals Console Tools via PowerShell (Part 4)

    In our previous tips we showed how you can use PowerShell to download, unblock, unzip and then run the Sysinternals console tools from PowerShell. This way you can use tools like psloggedon64.exe to figure out the users that are currently logged on. However, these tools output text to the console.

    In our last tip we look at how you can retrieve the command output and use it in your PowerShell code.

    Here is what we have…

  • Using Sysinternals Console Tools via PowerShell (Part 3)

    In our previous tips we showed how you can use PowerShell to download, unblock, unzip and then run the Sysinternals console tools from PowerShell. This way you can use tools like psloggedon64.exe to figure out the users that are currently logged on.

    However, when you run any Sysinternals tool for the first time, a EULA window pops up that needs to be accepted manually. Not good for completely automated use.

    To accept…

  • Using Sysinternals Console Tools via PowerShell (Part 2)

    In our previous tip we used PowerShell to download and set up the Sysinternals suite of console commands. Here is what we did:

    $destinationZipPath = "$env:temp\pstools.zip"
    $destinationFolder  = "$env:temp\pstools"
    $link = "https://download.sysinternals.com/files/PSTools.zip"
    Invoke-RestMethod -Uri $link -OutFile $destinationZipPath -UseBasicParsing
    Unblock-File -Path $destinationZipPa…
  • Using Sysinternals Console Tools via PowerShell (Part 1)

    Microsoft’s Sysinternals tools are a rich set of powerful console utilities that can also be run inside PowerShell and provide helpful information, albeit in string format.

    Here is a script that fully automates the download, unblocking and unzipping, to get you started.

    $destinationZipPath = "$env:temp\pstools.zip"
    $destinationFolder  = "$env:temp\pstools"
    $link = "https://download.sysinternals…
  • Outputting Color

    Occasionally, PowerShell code is supposed to output warnings and reports, so colors can add more readability to it. Traditionally, PowerShell supported color only through its Write-Host cmdlet:

    PS> Write-Host 'WARNING!' -ForegroundColor Red -BackgroundColor White

    That turned out to be impractical for many purposes, though, because if you wanted to mix colors in one line, you’d have to split the…

  • Better PowerShell Prompts (Part 2)

    In the previous tip we illustrated how you can define your own “prompt” function to customize the PowerShell prompt.

    One useful item could be to indicate in your prompt whether you are currently granted full Administrator privileges. Here is a prompt function that does this, taken from the PowerShell documentation:

    function prompt 
      $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
  • Better PowerShell Prompts (Part 1)

    I know today’s tip isn’t brand new but considering how many people start using PowerShell, it’s worth mentioning again.

    By default, the PowerShell prompt shows the current path which can be long and take away lots of screen real estate. A better way is adjusting the prompt. The most fundamental prompt could be looking like this:

    function prompt
        $Host.UI.RawUI.WindowTitle = Get-Location
  • Reading Recently Installed Software (Improvement #3)

    When reading event log data with Get-WinEvent, in the previous tip we explained how you can use the “Properties” property to extract the event details and use them in your own custom reporting.

    The same can be achieved with a fairly unknown trick. To illustrate, let’s again look at recently installed software. This gets you the data set for the latest installed MSI software on your Windows machine:

  • Reading Recently Installed Software (Improvement #2)

    In the previous tip we used Get-WinEvent to read the Windows event log system and get a list of recently installed software, then used Select-Object to pick the properties that yield useful information:

    Get-WinEvent -FilterHashtable @{ ProviderName="MSIInstaller"; ID=1033 } |
    Select-Object -Property TimeCreated, Message

    Unfortunately, most of the information we are after is embedded into the “Message” property…

  • Reading Recently Installed Software (Improvement #1)

    In the previous tip we used Get-WinEvent to read the Windows event log system and get a list of recently installed software, similar to this:

    function Shoath

    The result is a list of objects, one per installed software, however most of the properties yield unnecessary information:

    Message              : Windows Installer installed the product. Product Name: Elgato Stream Deck. 
  • Reading Recently Installed Software

    The MSI installer logs all successful software installation to the Windows event log system. Here is a one-liner that can read back that information:

    Get-WinEvent -FilterHashtable @{ ProviderName="MSIInstaller"; ID=1033 } |
    Select-Object -Property * 

    Twitter This Tip! ReTweet this Tip!

  • Gathering Forensic Process Info

    In order to better understand the processes that run on a server, and possibly identify traces of unwanted processes, PowerShell can dump forensic process information to CSV file in a way that Excel (if installed) can open the file. This way it is easy to review the processes and their command lines and start parameters.

    Here is the code:

    $Path = "$env:temp\processList.csv"
    # get all processes...
  • Don’t forget [Math]

    <!doctype html>

    [Math] is a handy static .NET library that you can use inside PowerShell whenever you need more advanced math functions:

    PS> [Math] | Get-Member -Static
       TypeName: System.Math
    Name            MemberType Definition
    ----            ---------- ----------
    Abs             Method     static sbyte Abs(sbyte value), static int16 Abs(int16 value), static int A...
    Acos            Method     static double…
  • Trusting All SSL Sites

    When PowerShell cmdlets download data via HTTPS:, they check whether the server certificate is valid, and if it is not, you receive an exception:

    # this URL always produces an SSL error:
    $url = 'https://expired.badssl.com/'
    # fails
    $result = Invoke-RestMethod -Uri $url -UseBasicParsing  

    In automation and administration, though, it occasionally becomes necessary to contact servers with invalid SSL certificates…

  • Logging Variable Types

    As part of your debugging and quality control you may want to log the data that gets assigned to individual variables. For example, you may want to find out what the actual data types are that are assigned to a given variable, so that you could later strongly-type the variable for added security.

    Here is a custom validator class that you can use for such analysis. Simply run the code below as a prerequisite. It does nothing…

  • Converting Language IDs in Language Names

    In our previous mini series we showed different approaches to get to the names of installed OS languages using different PowerShell methods. The result was always a list of language IDs, similar to this one:


    What if I needed to convert these to full country names? Fortunately, it is always just a matter of data types. All the methods we illustrated to get to the installed language packs returned…

  • Determining Language Packs (Part 3)

    In part 2 of this series, you already witnessed how much easier and faster it was to query the list of installed operating system languages using WMI compared to using command line tools like dism.exe. However, WMI still requires you to know the appropriate WMI class name.

    That’s why PowerShell sports the catch-all cmdlet Get-ComputerInfo. It queries all kinds of computer-related information for you and then lets it up…

  • Determining Language Packs (Part 2)

    In part 2 of this series, we’d like to solve our puzzle – getting installed language packs – by using the built-in PowerShell features. In part 1 we used a console application (dism.exe) which worked but was complex and required Administrator privileges.

    An object-oriented approach on Windows machines often is WMI where you query a class that describes the information you need, and get back the information…