Category Archives: Powershell

What happens if I run this command in Powershell

As Powershell allows us to do things, like stop process, move files and more, we might prefer to check “what if I run this command” before actually executing it.

Whatif

Let’s assume we want to stop several processes, but before executing the command we’d like to see some output showing what the command will do.

For example

Get-Process *host | Stop-Process -whatif

Instead of actually stopping the processes found via the wildcard *host this will output a list of “what if” statements, showing the operation which would take place against which target (process).

So we might notice that our wildcard includes unintended consequences and can thus save ourselves the headache unwanted changes.

Use Powershell to change file date/time stamps

Ever wanted to change the creation date/time on a file on Windows – Powershell offers the following (change Filename.ext to your filename)

$(Get-Item Filename.ext).creationtime
$(Get-Item Filename.ext).lastwritetime
$(Get-Item Filename.ext).lastaccesstime

Want to change these? You can supply the date in a string like this

$(Get-Item Filename.ext).creationtime = "15 March 2022 19:00:00"
$(Get-Item Filename.ext).lastwritetime = "15 March 2022 19:00:00"
$(Get-Item Filename.ext).lastaccesstime = "15 March 2022 19:00:00"

or if you like you’ve got

$(Get-Item Filename.ext).creationtime = $(Get-Date)
$(Get-Item Filename.ext).creationtime = $(Get-Date "15/03/2022 19:00:00")

Note: I’m on a en-GB machine hence using dd/MM/yyyy format – ofcourse changes this to your culture format.

Using Powershell to get a file hash

If you want to check a get the hash for a file (for checking your downloads or generating the hash), you can use the following Powershell command

Get-FileHash .\yourfile.exe

In this example the default SHA256 algorithm is used against some file named yourfile.exe (in this example).

You can change the algorithm use by supply the -Algorithm switch with any of the following values SHA1, SHA256, SHA384, SHA512, MACTrippleDES, MD5 and RIPEMD160.

Powershell from File Explorer’s context menu

Let’s add an “Open Poweshell” option to the File Explorer context menu.

  • Run Regedit
  • Go to HKEY_CLASSES_ROOT\Directory\shell
  • Add a new key to this, we’ll call ours power_shell as you may already see a PowerShell option and I don’t want to change that one
  • Change Default for the key to the text you want display in your context menu, i.e. Open Powershell Here
  • Add a new string value named Icon and in it’s value put C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe
  • Add a new key to the power_shell key, named command. This is what’s actually run in the Default key put C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location -LiteralPath ‘%L’

This will now show the Open Powershell Here option when you click on a folder in File Explorer, but what about if you’re already in the folder and want to open powershell from the current folder, then…

  • If you’ve closed Regedit then run Regedit again
  • Go to HKEY_CLASSES_ROOT\Directory\background\shell
  • Add a key named power_shell as before
  • As before we now add a string value named Icon with the same value as above
  • Again add a command subkey and with a slightly change add the following value into the Default key C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -NoExit -Command Set-Location -LiteralPath ‘%V’ – note we change %L to %V

A few dir/ls/Get-ChildItem Powershell commands

Get-ChildItem is aliased as ls/dir (as well as gci), so is used to list files and directories.

Here’s a few useful commands

Find only folders/directories

ls -Directory

Find only files

ls -File

Find files with a given extension

ls -Recurse | where {$_.Name -like "*.bak"}

Only search to a certain depth

The above will recurse over all folders, but we might want to restrict this by a certain depth level, i.e. in this example up to 2 directories deep

ls -Recurse -Depth 2 | where {$_.Name -like "*.bak"}

Finding the directories with files with a given extension

ls -Recurse -Depth 2 | where {$_.Name -like "*.bak"} | select directory -Unique

The use of -Unique ensure we do not have multiple directories with the same path/name (i.e. for each .bak file found within a single directory)

Powershell ForEach-Object gotcha!

Are you serious Powershell !!!!?

Okay, now I’ve got that out of the way, I wrote a little Powershell command to delete the *.deleteme files as well as the folders they refer to that Nuget occasionally seems to leave behind when updating packages.

So I developed the script on the command line and all looked good so decided to turn it into a function to add to $profile.

The function kept failing with the error “Supply values for the following paraneters: Process[0]”.

This appears because my foreach looked like this

foreach 
{ 
   // some functionality
}

and even though we think that the curly brace is the block (as it is in C#) it appears we need to place the first curly brace on the same line as the foreach, thus

foreach { 
   // some functionality
}

Here's the script, when it was completed

[code language="csharp"]
function Delete-DeleteMeAndFolder
{
   ls "*.deleteme" | 
   foreach { 
      rm -Recurse -Force $_.Name.Replace(".deleteme", "")
      del $_.Name
   }
}

Running Powershell commands from you application

So, I’ve written some Powershell cmdlet’s and occasionally, might want to either use them or use Powershell built-in cmdlet’s in one of my applications. Let’s look at making this work.

Hosting Powershell

To host Powershell we include the System.Management.Automation reference (locally or from Nuget) and we can simply use

using(var ps = Powershell.Create())
{
   // add command(s) and invoke them
}

to create a Powershell host.

Calling built-in commands/cmdlet’s

As you can see, creating the Powershell host was easy enough, but now we want to invoke a command. We can write something like

ps.AddCommand("Get-Process");

foreach (var r in ps.Invoke())
{
   Console.WriteLine(r);
}

Invoke will return a collection of PSObjects, from each of these objects we can get member info, properties etc. but also the actual object returned from GetProcess, in this case a Process object. From this we can do the following (if required)

foreach (var r in ps.Invoke())
{
   var process = (Process) r.BaseObject;
   Console.WriteLine(process.ProcessName);
}

Passing arguments into a command

When adding a command, we don’t include arguments within the command string, i.e. ps.AddCommand(“Import-Module MyModule.dll”) is wrong. Instead we pass the arguments using the AddArgument method or we supply key/value pair arguments/parameters using AddParameter, so for example

ps.AddCommand("Import-Module")                 
  .AddArgument("HelloModule.dll");

// or

ps.AddCommand("Import-Module")                 
  .AddParameter("Name", "HelloModule.dll");

So the parameter is obviously the switch name without the hyphen/switch prefix and the second value is the value for the switch.

Importing and using our own Cmdlet’s

So here’s a simply Cmdlet in MyModule.dll

[Cmdlet(VerbsCommon.Get, "Hello")]
public class GetHello : Cmdlet
{
   protected override void ProcessRecord()
   {
      WriteObject("Hello World");
   }
}

My assumption was (incorrectly) that running something like the code below, would import my module then run the Cmdlet Get-Hello

ps.AddCommand("Import-Module")                 
  .AddArgument("HelloModule.dll");

ps.Invoke();

ps.AddCommand("Get-Hello");

foreach (var r in ps.Invoke())
{
   Console.WriteLine(r);
}

in fact ProcessRecord for our Cmdlet doesn’t appear to get called (although BeginProcessing does get called) and therefore r is not going to contain any result even though it would appear everything worked (i.e. no exceptions).

What seems to happen is that the Invoke method doesn’t (as such) clear/reset the command pipeline and instead we need to run the code Commands.Clear(), as below

ps.AddCommand("Import-Module")                 
  .AddArgument("HelloModule.dll");

ps.Invoke();
ps.Commands.Clear();
// the rest of the code

An alternative to the above, if one is simply executing multiple commands which have no reliance on new modules or a shared instance of Powershell, might be to create a Poweshell object, add a command and invoke it and then create another Powershell instance and run a command and so on.

With a Powershell 3 compatible System.Management.Automation we can use the following. AddStatement method

ps.AddCommand("Import-Module")                 
  .AddArgument("HelloModule.dll");
  .AddStatement()
  .AddCommand("Get-Hello");

foreach (var r in ps.Invoke())
{
   Console.WriteLine(r);
}

Importing modules into a runspace

To share the importing of modules among Powershell host instances, we could, instead look to create a runspace (which is basically an environment space if you like) and import the module into the runspace, doing something like this

var initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new [] { "HelloModule.dll" });

var runSpace = RunspaceFactory.CreateRunspace(initial);
runSpace.Open();

using(var ps = PowerShell.Create())
{
   ps.Runspace = runSpace;

   ps.AddCommand("Get-Hello");

   foreach (var r in ps.Invoke())
   {
      Console.WriteLine(r);
   }
}

In the above code, we import our modules into the initial session, from this we create our runspace and then we associated that with the our Powershell host(s) and reuse as required.

References

Windows PowerShell Host Quickstart

Environment variables in Powershell

This just demonstrates how, when you’re used to the standard Windows command prompt, Powershell can (at times) bite you.

So I was getting some Java code configured and noticed the ant script listed a different JAVA_HOME to the one I expected. In a standard Windows command prompt I would type

echo %JAVA_HOME%

and ofcourse, expect to see the folder location. So this is what I did in Powershell and instead I just got the result %JAVA_HOME% written out.

Powershell does things differently. In this case to output environment variables we need to use

echo $Env:JAVA_HOME

Doh!

To set an environment variable in Powershell we simply type

$env:JAVA_HOME="C:\Program Files\Java\jdk1.6.0_45"

References

See Windows PowerShell Tip of the Week

Setup Powershell to use the Visual Studio paths etc.

This one’s straight off of How I can use PowerShell with the Visual Studio Command Prompt? and it works a treat.

So I amend the $profile file with the following (updated to include VS 2015)

function Set-VsCmd
{
    param(
        [parameter(Mandatory=$true, HelpMessage="Enter VS version as 2010, 2012, 2013, 2015")]
        [ValidateSet(2010,2012,2013,2015)]
        [int]$version
    )
    $VS_VERSION = @{ 2010 = "10.0"; 2012 = "11.0"; 2013 = "12.0"; 2015 = "14.0" }
    if($version -eq 2015)
    {
        $targetDir = "c:\Program Files (x86)\Microsoft Visual Studio $($VS_VERSION[$version])\Common7\Tools"
        $vcvars = "VsMSBuildCmd.bat"
    }
    else
    {
        $targetDir = "c:\Program Files (x86)\Microsoft Visual Studio $($VS_VERSION[$version])\VC"
        $vcvars = "vcvarsall.bat"
    }
 
    if (!(Test-Path (Join-Path $targetDir $vcvars))) {
        "Error: Visual Studio $version not installed"
        return
    }
    pushd $targetDir
    cmd /c $vcvars + "&set" |
    foreach {
      if ($_ -match "(.*?)=(.*)") {
        Set-Item -force -path "ENV:\$($matches[1])" -value "$($matches[2])"
      }
    }
    popd
    write-host "`nVisual Studio $version Command Prompt variables set." -ForegroundColor Yellow
}

The previous version (non-VS 2015) is listed below in case it’s still needed

function Set-VsCmd
{
    param(
        [parameter(Mandatory=$true, HelpMessage="Enter VS version as 2010, 2012, or 2013")]
        [ValidateSet(2010,2012,2013)]
        [int]$version
    )
    $VS_VERSION = @{ 2010 = "10.0"; 2012 = "11.0"; 2013 = "12.0" }
    $targetDir = "c:\Program Files (x86)\Microsoft Visual Studio $($VS_VERSION[$version])\VC"
    if (!(Test-Path (Join-Path $targetDir "vcvarsall.bat"))) {
        "Error: Visual Studio $version not installed"
        return
    }
    pushd $targetDir
    cmd /c "vcvarsall.bat&set" |
    foreach {
      if ($_ -match "(.*?)=(.*)") {
        Set-Item -force -path "ENV:\$($matches[1])" -value "$($matches[2])"
      }
    }
    popd
    write-host "`nVisual Studio $version Command Prompt variables set." -ForegroundColor Yellow
}

Another user on the same question of stackoverlow also put forward the idea of simply changing the shortcut that Visual Studio supply to add the & powershell, like this

%comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\VsDevCmd.bat" & powershell"