PowerShell v6.1– using PowerShell v5.1 modules

Something new to be aware of in PowerShell v6.1– using PowerShell v5.1 modules .

In PowerShell v6.0.4 the default module path is

PS>  $env:PSModulePath -split ‘;’
C:\Users\Richard\Documents\PowerShell\Modules
C:\Program Files\PowerShell\Modules
c:\program files\powershell\6.0.4\Modules

If you want to use the netadapter cmdlets for instance to need to explicitly load the module

Import-Module ‘C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\netadapter\netadapter.psd1’

You can then use Get-Netadapter.

There appears to be an issue with PowerShell v6.0.4 as it only displays the interface index, the status and the macaddress!

In PowerShell v6.1 RC1 the module path changes to

PS>  $env:PSModulePath -split ‘;’
C:\Users\Richard\Documents\PowerShell\Modules
C:\Program Files\PowerShell\Modules
c:\program files\powershell\6-preview\Modules
C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

In theory you’d expect tis to work:

PS>  Get-Module -ListAvailable net*
PS>  Get-Module -ListAvailable netadapter

But you get nothing back

Importing the module directly gives:

PS>  Import-Module netadapter
Import-Module : Module ‘C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\netadapter\netadapter.psd1’ does not support current PowerShell edition ‘Core’. Its supported editions are ‘Desktop’. Use ‘Import-Module -SkipEditionCheck’ to ignore the compatibility of this module.
At line:1 char:1
+ Import-Module netadapter
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : ResourceUnavailable: (C:\WINDOWS\syst…netadapter.psd1:String) [Import-Module], InvalidOperationException
+ FullyQualifiedErrorId : Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand

PS>  Import-Module netadapter -SkipEditionCheck
PS>  Get-NetAdapter

Works and you see the expected data! Definitely looks like a bug in v6.0.4

Get-Module also can skip the edition check

Get-Module -ListAvailable net* –SkipEditionCheck

Will display the modules.

If you’re using v6.1 RC 1 AND you want to use PowerShell v5.1 modules you may want to think about using $PsDefaultParameterValues to set  SkipEditionCheck on for Get-Module and Import-Module.

This change will more than likely break your scripts.

Using SkipEditionCheck won’t allow you to run a binary module such as Active Directory that’s complied using full .NET

Advertisements
Posted in PowerShell v6 | Leave a comment

PowerShell v6.1 release candidate 1

PowerShell v6.1 release candidate 1 is now available from https://github.com/PowerShell/PowerShell/releases

The release notes don’t show any major new functionality compared to preview 4 though there are a number of minor updates, changes and bug fixes.

Posted in PowerShell v6 | Leave a comment

Deleting folders based on month name

An interesting problem around deleting folders based on month name.

You have a folder structure that looks like this

C:\Testdata\2011\08-Aug
C:\Testdata\2011\09-Sep
C:\Testdata\2011\10-Oct
C:\Testdata\2011\11-Nov
C:\Testdata\2011\12-Dec
C:\Testdata\2012\01-Jan
C:\Testdata\2012\02-Feb
C:\Testdata\2012\03-Mar
C:\Testdata\2012\04-Apr
C:\Testdata\2012\05-May
C:\Testdata\2012\06-Jun
C:\Testdata\2012\07-Jul
C:\Testdata\2012\08-Aug
C:\Testdata\2012\09-Sep
C:\Testdata\2012\10-Oct
C:\Testdata\2012\11-Nov
C:\Testdata\2012\12-Dec
C:\Testdata\2013\01-Jan
C:\Testdata\2013\02-Feb
C:\Testdata\2013\03-Mar
C:\Testdata\2013\04-Apr
C:\Testdata\2013\05-May
C:\Testdata\2013\06-Jun
C:\Testdata\2013\07-Jul
C:\Testdata\2013\08-Aug

The structure extends forward and backward in time.  Each year folder has 12 subfolders based on the number and name of the month.

You want to delete all folder, subfolders and contents that are more than 7 years old BUT you have to use the folder name for month and its parent for the year. You can’t assume that the create date or last write date will work in this instance.

If you want to paly around with this the code to create the folder structure looks like this:

##

## define enum for months

##

enum month {
Jan = 1
Feb = 2
Mar = 3
Apr = 4
May = 5
Jun = 6
Jul = 7
Aug = 8
Sep = 9
Oct = 10
Nov = 11
Dec = 12

}

##

## create folder structure

##

New-Item -Path c:\ -Name Testdata -ItemType Directory

2018..2005 |

foreach {
New-Item -Path c:\Testdata -Name $psitem -ItemType Directory

$path = “c:\Testdata\$psitem”

1..12 |
foreach {
$name =  “{0:00}-{1}” -f $psitem, [month]$psitem
New-Item -Path $path -Name $name -ItemType Directory
}

}

Create a top level folder. Then for the years 2018 back to 2005 create a year folder using the year as the name. In each year folder create 12 month folders. I’ve used the string formatting –f operator to put a leading zero on the month number and to get the appropriate value from the enum.

Now to delete the folders

enum month {
Jan = 1
Feb = 2
Mar = 3
Apr = 4
May = 5
Jun = 6
Jul = 7
Aug = 8
Sep = 9
Oct = 10
Nov = 11
Dec = 12

}

$date = Get-Date
$year = $date.Year – 8

##
##  delete everything 8 years or older
##

Get-ChildItem -Path C:\Testdata -Directory |
where Name -le $year |
foreach {
Remove-Item -Path $psitem.Fullname -Recurse -Force -Confirm:$false
}

##
##  if Month -ne January
##   need to delete some months
##

if ($date.Month -gt 1){
$path = “C:\testdata\$($year+1)”
$month = $date.Month -1

1..$month |
foreach {
$mpath = “$path\{0:00}-{1}” -f $psitem, [month]$psitem
Remove-Item -Path $mpath -Recurse -Force -Confirm:$false
}
}

The code gets the current date and gets the year minus 8. It loops through your top level folder and gets the folders that are less than or equal to the year you’ve defined. They, and their contents, are force deleted. Only thing that could stop the deletion is if you have one of the files pinned open.

If the current month is January there’s nothing else to do. Otherwise create the path to the -7 year folder and calculate the last month you want to delete. Loop through the months, build the path and force the deletion of the folder and its contents.

The bulk of the work is done at the year level with a quick clean up of the months.

Posted in Powershell | Leave a comment

File searches with WMI

I saw a question about file searches with WMI.

If you just know the file name it’s a very slow process. Painfully slow.

If you have an idea about the folder its much quicker.

function get-wmifile {
[CmdletBinding()]
param (
   [Parameter(Mandatory = $true)]
   [string]$path,
  
   [string]$file
  
)

  if ($path.IndexOf(‘\\’) -le 0 ){
     $path = $path.replace(‘\’, ‘\\’)
   }

  if ($path.IndexOf(‘*’) -ge 0 ){
     $path = $path.replace(‘*’, ‘%’)
   }

  Write-Verbose -Message “Path to search: $path”

  $folders = Get-CimInstance -ClassName Win32_Directory -Filter “Name LIKE ‘$path'”
   foreach ($folder in $folders){
    
     if ($file) {
       Get-CimAssociatedInstance -InputObject $folder -ResultClassName CIM_DataFile |
       where Name -Like “*$file” |
       Select Name
     }
     else {
       Get-CimAssociatedInstance -InputObject $folder -ResultClassName CIM_DataFile |
       Select Name
     }
   }

}

Take the path and replace any single \ with \\.  WMI expects c:\\test NOT c:\test.

Likewise replace the wildcard * with the WMI wildcard equivalent %

Get the folders that are like the path. Foreach folder use the WMI association between Win32_Directory and CIM_datafile to get the files.  if you’ve specified a file then just return that otherwise all files in the folders matching path.

The WMI approach works and if you add in the computer name you can use remoting to get the answer from a remote machine.

As a comparison using Get-ChildItem would be

PS> Get-ChildItem -Path c:\test* -Recurse -Filter ‘p1.txt’ | select Fullname

which is much quicker.

Using WMI to do this is a fun exercise but the fastest approach is to use Get-ChildItem

Posted in PowerShell and CIM, PowerShell and WMI | Leave a comment

Variable type

Usually when you create a variable you implicitly set the type by the value you use. Sometimes though you may want to explicitly set the variable type.

if you don’t give the variable a type you can do this:

PS> $x = 35
PS> $x
35
PS> $x = ‘now a string’
PS> $x
now a string

If you give the variable an explicit type

PS> [int]$x = 35
PS> $x
35
PS> $x = ‘now a string’
Cannot convert value “now a string” to type “System.Int32”. Error: “Input string was not in a correct format.”
At line:1 char:1
+ $x = ‘now a string’
+ ~~~~~~~~~~~~~~~~~~~
     + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
     + FullyQualifiedErrorId : RuntimeException

The variable expects an integer or something that can be converted to an integer

PS> $x = ‘123’
PS> $x
123

You can’t supply a type when you use New-Variable so if you need a read only or constant variable then create it as shown above then use Set-Variable to make it read only or constant.

Posted in Powershell | Leave a comment

Read only and constant variables

When you create a variable you usually want to be able to change its value – the clue is in the term variable. Sometimes though you might want to use a value that doesn’t change – you want read only and constant variables.

Standard variables are changeable:

PS> $x  = 10
PS> $x
10
PS> $x += 5
PS> $x
15
PS> $x -= 3
PS> $x
12

The variable holds a value that can be changed to match your needs. Another example is the variable used in a for loop:

PS> for ($i=1; $i -lt 6; $i++){$i}
1
2
3
4
5

If you want to use a value in your code that is pre-set and doesn’t change you have a couple of options – read only variables and constants.

To create a read only variable use New-variable with the ReadOnly option

PS> New-Variable -Name x -Value 10 -Option ReadOnly
PS> $x
10

Now if you try to change the value

PS> $x += 5
Cannot overwrite variable x because it is read-only or constant.
At line:1 char:1
+ $x += 5
+ ~~~~~~~
     + CategoryInfo          : WriteError: (x:String) [], SessionStateUnauthorizedAccessException
     + FullyQualifiedErrorId : VariableNotWritable

You can also change an existing variable to be read only

PS> $y = 20
PS> Set-Variable -Name y -Option ReadOnly
PS> $y
20
PS> $y -= 5
Cannot overwrite variable y because it is read-only or constant.
At line:1 char:1
+ $y -= 5
+ ~~~~~~~
     + CategoryInfo          : WriteError: (y:String) [], SessionStateUnauthorizedAccessException
     + FullyQualifiedErrorId : VariableNotWritable

A read only variable is protected from deletion

PS> Remove-Variable -Name x
Remove-Variable : Cannot remove variable x because it is constant or read-only. If the variable is read-only, try the
operation again specifying the Force option.
At line:1 char:1
+ Remove-Variable -Name x
+ ~~~~~~~~~~~~~~~~~~~~~~~
     + CategoryInfo          : WriteError: (x:String) [Remove-Variable], SessionStateUnauthorizedAccessException
     + FullyQualifiedErrorId : VariableNotRemovable,Microsoft.PowerShell.Commands.RemoveVariableCommand

Time to be a PowerShell Jedi and use the force:

PS> Remove-Variable -Name x -Force

the variable is removed.

But what if you want to prevent the variable from being removed. Then you have to make it a constant:

PS> New-Variable -Name x -Value 10 -Option Constant
PS> $x
10
PS> $x += 5
Cannot overwrite variable x because it is read-only or constant.
At line:1 char:1
+ $x += 5
+ ~~~~~~~
     + CategoryInfo          : WriteError: (x:String) [], SessionStateUnauthorizedAccessException
     + FullyQualifiedErrorId : VariableNotWritable

PS> Remove-Variable -Name x
Remove-Variable : Cannot remove variable x because it is constant or read-only. If the variable is read-only, try the
operation again specifying the Force option.
At line:1 char:1
+ Remove-Variable -Name x
+ ~~~~~~~~~~~~~~~~~~~~~~~
     + CategoryInfo          : WriteError: (x:String) [Remove-Variable], SessionStateUnauthorizedAccessException
     + FullyQualifiedErrorId : VariableNotRemovable,Microsoft.PowerShell.Commands.RemoveVariableCommand

PS> Remove-Variable -Name x -Force
Remove-Variable : Cannot remove variable x because it is constant or read-only. If the variable is read-only, try the
operation again specifying the Force option.
At line:1 char:1
+ Remove-Variable -Name x -Force
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     + CategoryInfo          : WriteError: (x:String) [Remove-Variable], SessionStateUnauthorizedAccessException
     + FullyQualifiedErrorId : VariableNotRemovable,Microsoft.PowerShell.Commands.RemoveVariableCommand

You can’t modify the variable’s value and you can’t delete it.

The variable will disappear when you close the PowerShell console.

Posted in Powershell | Leave a comment

user logon time

Saw an interesting question about user logon time. How can you tell the logged on user and when they logged on

$logon = Get-CimInstance -ClassName Win32_LogonSession |
sort StartTime -Descending |
select -First 1

$user = Get-CimAssociatedInstance -InputObject $logon -ResultClassName Win32_Account

$props = [ordered]@{
   Name = $user.Fullname
   UserId = $user.Name
   Domain = $user.Domain
   LocalAccount = $user.LocalAccount
   LogonTime = $logon.StartTime
}

New-Object -TypeName PSobject -Property $props

Start by getting the last logon session using Win32_LogonSession. Use that to find the associated account using Get-CimAssociatedInstance and the Win32_Account class.

Pull together the output you want and create an object to display.

This deliberately only takes the latest logon and deals with that

Posted in PowerShell and CIM, PowerShell and WMI | Leave a comment