Powershell command line clipboard

Credit to this gentleman for showing how to get the current command, aka the last command in the history list, into the clipboard. Dead simple, as you might expect… once you know how:

Clip01

(Get-History)[-1].CommandLine | clip

And from there, you can of course paste it.. eg:

Clip02

 

 

Advertisements

PowerShell – Populating objects

Say I have an employee object. It seems that short of creating a C#/.Net object as a here script, you cannot create datatypes at the point you create the object.

Simplest approach seems to be this:

$employee = “” | Select-Object “Last Name”, FirstName, DOB, Salary

That potentially exercises a string, a string with spaces, a datetime, a decimal, and an integer. But right now it exercises nothing:

PS D:\scratch> $employee | gm
 TypeName: Selected.System.String
Name MemberType Definition 
---- ---------- ---------- 
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode() 
GetType Method type GetType() 
ToString Method string ToString() 
DOB NoteProperty DOB=null 
FirstName NoteProperty FirstName=null 
Last Name NoteProperty Last Name=null 
Salary NoteProperty Salary=null

PS D:\scratch> $employee.’Last Name’ = [string]”Jones”

Now gives us this:

PS D:\scratch> $employee | gm
TypeName: Selected.System.String

Name MemberType Definition
—- ———- ———-
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
DOB NoteProperty DOB=null
FirstName NoteProperty FirstName=null
Last Name NoteProperty System.String Last Name=Jones
Salary NoteProperty Salary=null

and this:

PS D:\scratch> $employee

Last Name FirstName DOB Salary
——— ——— — ——
Jones

That’s the string taken care of.

Damn. I missed shoesize for the integer:

Let’s add it anyway:

PS D:\scratch> $employee | Add-Member -MemberType NoteProperty -Name ShoeSize -Value $null -Force

PS D:\scratch> $employee | gm
TypeName: Selected.System.String

Name MemberType Definition
—- ———- ———-
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
DOB NoteProperty DOB=null
FirstName NoteProperty FirstName=null
Last Name NoteProperty System.String Last Name=Jones
Salary NoteProperty Salary=null
ShoeSize NoteProperty ShoeSize=null

Letting us have

PS D:\scratch> $employee.ShoeSize = 22

and therefore (notice that the default display formatting has changed, now we have added another value):

 

PS D:\scratch> $employee
Last Name : Jones
FirstName :
DOB :
Salary :
ShoeSize : 22

Now we’ll do a date:

PS D:\scratch> $employee.DOB = [datetime]::UtcNow

PS D:\scratch> $employee
Last Name : Jones
FirstName :
DOB : 23/06/2014 21:31:17
Salary :
ShoeSize : 22

PS D:\scratch> $employee.Salary = [decimal] 15123.55

PS D:\scratch> $employee
Last Name : Jones
FirstName :
DOB : 23/06/2014 21:31:17
Salary : 15123.55
ShoeSize : 22

Have a collection of employees:

PS D:\scratch> $employees = $employee

(how do these things get GC’d? Do they?)

PS D:\scratch> $employees
Last Name : Jones
FirstName :
DOB : 23/06/2014 21:31:17
Salary : 15123.55
ShoeSize : 22

PS D:\scratch> $employees | gm
TypeName: Selected.System.String

Name MemberType Definition
—- ———- ———-
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
DOB NoteProperty System.DateTime DOB=23/06/2014 21:31:17
FirstName NoteProperty FirstName=null
Last Name NoteProperty System.String Last Name=Jones
Salary NoteProperty System.Decimal Salary=15123.55
ShoeSize NoteProperty System.Int32 ShoeSize=22

So at this point, still a single thing.

Add in the same employee (which I take to be byVal, not byRef):

Interesting:

PS D:\scratch> $employees += $employee
Method invocation failed because [System.Management.Automation.PSObject] does not contain a method named ‘op_Addition’.
At line:1 char:1
+ $employees += $employee
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Addition:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound

Was this because my initial definition of the collection was [=], rather than [+=]? Give that a try:

PS D:\scratch> $employees = $null

PS D:\scratch> $employees += $employee

OK, but still not an array (of 1)

Tried again with [$employees += $employee], and got the same op_addition error

Ah – fatal flaw: didn’t first defined $employees as an array:

PS D:\scratch> $employees = @()

now try it all again…

PS D:\scratch> $employees += $employee

PS D:\scratch> $employees += $employee

But still not an array.

$employees[1].’Last Name’ = “yup”

Hm, ok – it’s the same underlying object.

Let’s make employee 1 a proper thing then

$employee2 = “” | Select-Object “Last Name”, FirstName, DOB, Salary, ShoeSize

$employees += $employee2

That’s all I’ve got time for tonight – yes, needs formatting.

 

 

 

 

 

 

 

 

 

PowerShell – import-csv – Help is truly your friend

get-help Import-Csv -Examples

returns a mass of useful stuff. It shows that the mere act of running Import-Csv into a variable creates an object, and properties that come from the header of the CSV (if any).

For example, if you create a CSV file like this:

Get-Process | Export-Csv -Path d:\scratch\process.csv

This is a sample from the file generated, where presumably you can just throw away the first line which is presumably interpreted as a comment?, line 2 is the header, line 3 onwards is the data:

CsvHeader01

I’ll remove line 1, and then import it into a variable:

$p = Import-Csv -Path .\process.csv

Running Get-Member over that…

CsvImported01

A PSCustomObject has been created, with NoteProperties corresponding to the header names that appear in the .csv file. Note that these are all co-erced to string, as we’ve not given it any better information.

The help file shows that you can replace the header values.

The one thing I cannot see how to do is to change the datatype from the default NoteProperty, which always seems to be a string. You could clearly dictate that, but in terms of “do we really need that?”, probably not right now.

Staying with the PSObject theme, quite a lot of pain has gone into this task of converting a file of fixed width to a csv based on an associated file widths metadata file.

The metadata file (formatRules.txt – the blanks etc are intentional):

MainObjectName
Ignore this – Field, Width, Type,Format

columnName1,20,String,
,5,String
amOunt,10,Decimal
IsHairy,5,Boolean
DateOftRansaction,14,DateTime,

The data file (values.txt):

SomeValuefollowed isxxx 209.99false20141208202212
Followed By vario 10.12true 20121212121212

… and the script as it stands:


$formatRule = gc D:\scratch\FormatRules.txt
$strippedFormatRule = $formatRule | select -Skip 2

$myArray = @()
$first = $True
$strippedFormatRule | % {
$columnOffset = if ($first -eq $true) {
0
} else {
$columnOffset + $rule[1]
}
$rule = $_.split(",")

$first = $false
$myArray += New-Object psobject -Property @{
columnName =
if ([System.String]::IsNullOrEmpty($rule[0])) {
"dummy" } else {
$rule[0]
}
columnWidth = $rule[1]
columnType = $rule[2]
colOffset = $columnOffset
}
}

$myArray | ft -AutoSize

$myData = gc D:\scratch\values.txt

$outputDataFile = "d:\scratch\output.dat"
Clear-Content $outputDataFile
$myData | % {
$tempData = $_
$currentRow = $myArray | % {
[string]::format("`"{0}`"", [string]($tempData.substring($_.colOffSet,$_.columnWidth)).Trim());
}
$tidiedData = [System.String]::Join(",", $currentRow)
Add-Content $outputDataFile $tidiedData

}

 

$headerAsArray = $myArray | % {
[string]::format("`"{0}`"", [string]$_.columnName)
}
"Current header:"
$headerAsArray
"Data..."
gc $outputDataFile

Import-Csv -Path $outputDataFile -Header $headerAsArray | Out-GridView

which yields this kind of output:

GridView01

I did say Help is your friend – well, I’m not convinced on the -Header option.

So I’m very pleased with my progress on this:

<#
.Synopsis
   Show the content of a file, converted from fixed width to csv, in order to display
   it through out-gridview.
   You must specify the data file, a file containing the widths of each of the columns,
   and optionally the number of header lines to ignore from the format file (default 0) and the maximum number of
   records to return from the input data file (in case the file is very large, and you just want
   a sample of the data).
   The column headers in the format file be empty - see the example file below.
.Example
    Show-File -dataFile .\composers.dat -formatFile .\composers.fmt -formatHeaderLinesToIgnore 1 -maxRecords 10
    Test format file (starts at left of line, CRLF after the last character):
        List of Composers
        LastName,20
        FirstName,20
        ,10
        ,3,
        DateOfBirth,10
    Test data file (starts at left of line, CRLF after the last character):
        Mozart              Wolfgang            Austrian     10/10/1786
        Beethoven           Ludwig              German    xxx01/02/1796
        Chopin              Frederic            Polish       11/10/1801
#>
function Show-File
{
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_ })]
        [string] $dataFile,

        [Parameter(Mandatory=$true)]
        [ValidateScript({Test-Path $_ })]
        [string] $formatFile,

        [Parameter(Mandatory=$false)]
        [int] $formatHeaderLinesToIgnore = 0,

        [Parameter(Mandatory=$false)]
        [int] $maxRecords = 100
    )
    $outputDataFile = New-Item -ItemType File $env:Temp\output.dat -Force

    $columnNameIndex = 0
    $columnWidthIndex = 1

    $formatRuleSet = Get-Content $formatFile | Select-Object -Skip $formatHeaderLinesToIgnore

    $formatRulesArray = @()
    $first = $True
    $formatRuleSet | % {
        $columnOffset =
            if ($first -eq $true) {
                0
            } else {
                $columnOffset + $rule[$columnWidthIndex]
            }
        $first = $false
        $rule = $_.split(",")
        $formatRulesArray += New-Object psobject -Property @{
            columnName =
                if ([System.String]::IsNullOrEmpty($rule[$columnNameIndex])) {
                    [string]::format("dummy{0}", [system.guid]::NewGuid()) } else {
                    $rule[$columnNameIndex]
                }
            columnWidth = $rule[$columnWidthIndex]
            colOffset = $columnOffset
        }
    }

    $msg = [string]::Format("Starting to write data... [{0}]", [System.DateTime]::utcnow); Write-Verbose $msg

    Clear-Content $outputDataFile

    Get-Content $dataFile | Select-Object -First $maxRecords | foreach {
        $tempData = $_
        $currentRow = $formatRulesArray | foreach {
            [string]::format("`"{0}`"", [string]($tempData.substring($_.colOffSet,$_.columnWidth)).Trim());
        }
        $tidiedData = [System.String]::Join(",", $currentRow)
        Add-Content $outputDataFile $tidiedData
    }
    $quoteHeader = $formatRulesArray | % {
        [string]::format("`"{0}`"", [string]$_.columnName)
    } 

    $msg = [string]::Format("Finished writing data; starting import for rendering... [{0}]", [System.DateTime]::utcnow); Write-Verbose $msg

    Import-Csv -Path $outputDataFile -Header $quoteHeader | Out-GridView

    $msg = [string]::Format("Finished rendering... [{0}]", [System.DateTime]::utcnow); Write-Verbose $msg
}

This is the default using the test files:

GOV_01

You can filter…

GOV_02

 

You can drag the columns around…

GOV_03

And to give an idea of how the code looks in the ISE:

GOV_04

 

 

PowerShell – display lines and line numbers

The context for this was the lack of a means, out-of-the-box, to display line numbers from a file (and I accept I may be wrong on that lack).

The person wanted to display lines, and their line numbers, on a condition: here I’ve defined that condition as “if the line contains the letter ‘o’, display it”.

So it comes down to this – get some lines into a file:

PS D:\scratch> “rose”,”sam”,”tony” > x.txt

Then a 1-liner which has a counter variable which increments for every line in the file, and outputs the line and its line number, if the line contains the letter ‘0’, as we said:

$counter = 0; gc x.txt |  % {$counter++; if ($_ -like “*o*”) {“[$counter] $_”}}

Returns this output:
RoseAndTony01
S’it.

 

How to generate Lottery Numbers

Summary

Lottery numbers can be generated with PowerShell, using this line (this is just one way – there are others):

1..3 | % {“Lottery Line: $_”; 1..1000 | % { get-random -Minimum 1 -Maximum 49 } | select -Unique | select -First 6 | Sort}

1..3 | % {"Lottery Line: $_"; 1..1000 | % { get-random -Minimum 1 -Maximum 49 } | select -Unique | select -First 6 | Sort}

If you don’t know how to start PowerShell, click here to understand that, first.

The screen immediately below is the National Lottery number confirmation screen: it is not for entering data. If you are thinking either a) that’s obvious or b) well, that’s confusing – a data entry screen is exactly what I thought it was, click here.

Lottery02

Detail

… that obscures the elegance of the summary. Ignore this if the summary gave you what you needed.
I don’t want to use the same set of numbers that I use all the time, and I don’t want to do Lucky Dip. I want the illusion that somehow I control the numbers… but don’t want to think about it. I need a script that:

  • Generates random numbers between 1 and 49 (for the UK National Lottery).
  • Allows me to specify any number of lottery lines

The PowerShell script

As I said, ignore this if not of interest.

A couple of ways to generate the numbers – first using the .Net Random object:

$rand = New-Object System.Random(65000)
$lotteryArray = @()
1..1000 | % {$lotteryArray += $rand.Next(1,49)}
$lotteryArray | select -Unique | select -First 6

ExampleRandomNumbers01

Another way is to use PowerShell’s Get-Random. In fact this does it all in 1 line, so just use this (but I’m keeping the other one in for those interested):

1..1000 | % { get-random -Minimum 1 -Maximum 49 } | select -Unique | select -First 6

lotto02

And as using this approach, we’re doing it all on 1 line, now let’s say how many lottery lines we want to generate – so in the example below, the 3 reference in the 1..3 bit says how many lottery lines (do this all on one line):

1..3 | % {"Lottery Line: $_"; 1..1000 | % { get-random -Minimum 1 -Maximum 49 } | select -Unique | select -First 6}

That then gives you:

lotto01

 

And then we plug that into the National Lottery numbers:

Lottery02

Usability

My wife, who with my daughter is one half of my Usability Team™, pulled me on the fact that the pretty screen looks like a dialogue where you can actually click and enter numbers. Nah, it’s just to break up the monotony of the text. She couldn’t believe that in 2014, we would actually have a new thing that didn’t have a Graphical User Interface. Well, we do, and in the Microsoft world, it’s getting bigger with each release of Windows.

Starting PowerShell

Your PC (not Mac, or tablet) will have PowerShell. To start it, you…
Windows 7: in the Start box, start typing PowerShell. It will show a number of choices. Pick PowerShell.exe
Windows 8: on the Start screen, start typing PowerShell. It will show a number of choices. Pick Windows PowerShell