Wednesday, November 14, 2018

PHP: Passing Variables on the Command Line

In my work I need to generate documentation, and sadly I work in an all Windows environment and have no Apache server available to me. Using PHP to generate content is pretty natural to, but not using a web server creates some challenges.

If you have PHP installed*, it is easy enough to turn a document into an html document via redirection:

php .\mypage.php > mypage.html

However, one of the great things about PHP is passing $_GET data. I use JSON config files which contain data my documentation needs. If I want to point to a particular json file, I do not need to code it into my script, if I pass the file location as an argument.

<?php
    parse_str(implode('&', array_slice($argv, 1)), $_GET);
    $json = $_GET['json'];
    include($json);
?>

So, my command line would be:

php .\mypage.php json=mypage.json > mypage.html

* PHP can be drop-installed on Windows, meaning that if the executable is present you can run php. I set up an alias in my Powershell $PROFILE to make it available easily:

    Set-Alias -Name php -Value C:\mybin\php\php.exe

Friday, November 9, 2018

BASH: Assign Output of Command to a Variable

In Powershell it is pretty easy to assign output to a variable, but not being a BASH scripter I needed to look up how to do it there. I found a good article at CyberCiti.

The goal was to create a cron job to update one of my Raspberry Pi systems. As I am a packager I like to create logs, so I want to create a log with the date and time of the update job. To automate this, I needed to get the date from the system and parse it. I also want to see when ClamAV's definitions were last updated. So I have two instances where I turn command output into a string variable:

#!/bin/bash
LOG=$(date +/home/david/logs/%Y%m%d_at_%H%M.log)
touch $LOG
BAR="--------------------------------------------------"
echo $BAR >> $LOG
sudo apt-get update >> $LOG
echo $BAR >> $LOG
sudo apt-get upgrade -y >> $LOG
echo $BAR >> $LOG
sudo apt-get dist-upgrade -y >> $LOG
echo $BAR >> $LOG
sudo apt-get autoremove -y >> $LOG
echo $BAR >> $LOG
echo Last /var/log/clamav/freshclam.log >> $LOG
CLAM=$(tail -n 1 /var/log/clamav/freshclam.log)
DATE="${CLAM:0:24}"
echo "${DATE}" >> $LOG

My Log then looks like this (I shortened it a bit here) with above examples highlighted:

david@rpi3b ~/bin $ cat ~/logs/20181109_at_0923.log 
--------------------------------------------------
Hit:1 http://archive.canonical.com/ubuntu xenial InRelease
[...]
Hit:17 https://deb.etcher.io stable Release
Reading package lists...
--------------------------------------------------
Reading package lists...
Building dependency tree...
Reading state information...
Calculating upgrade...
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
--------------------------------------------------
Reading package lists...
Building dependency tree...
Reading state information...
Calculating upgrade...
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
--------------------------------------------------
Reading package lists...
Building dependency tree...
Reading state information...
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
--------------------------------------------------
Last /var/log/clamav/freshclam.log
Fri Nov  9 08:26:33 2018

Another nifty page from CyberCiti details the below technique:

The last trick there was to get just the timestamp from the ClamAV log. The log in it's raw form looks like:

Fri Nov  9 08:26:33 2018 -> --------------------------------------

I could leave that as it is (it is just a log, after all), but getting a substring is what the boss would want if this was for her, so I parse it out. The desired string is 24 characters long, so we get the string and parse it:

CLAM=$(tail -n 1 /var/log/clamav/freshclam.log)

Then create a masked version with substring expansion in a new variable from $CLAM and character position 0 and with a length of 24 (so, characters 0:23):

DATE="${CLAM:0:24}"

Then echo that into my log:

echo "${DATE}" >> $LOG

Wednesday, November 7, 2018

Powershell: Calculated Properties


Posting for reference; a bit a colleague sent me in chat:
Subtle...but important trick...turning literal strings into objects with a property:

'apple','orange','banana' | Select-Object -Property @{ Name = 'FruitName'; Expression = {$_} }

This way, if I had a cmdlet that took -FruitName as a parameter and accepted pipeline input by name...I could use this to map into that parameter...

[...]

In powershell parlance that's called a "calculated property"

[...]

Normally, you'd use it to transform a property. So for instance, say an existing property was in bytes and you wanted it in MB

Name='MB'; Expression = {$_.Bytes / 1MB }

[...]

And then the other use I can think of is if you had objects that had say a property called 'Name' that was actually a service name...but the cmdlet you wanted to pipe into needed '-ServiceName' ...you could use the calculated property trick to make your objects have a 'ServiceName' property.

@{ Name = 'ServiceName'; Expression = {$_.Name} }
Creating pipeable commandlets is a bit out of my wheelhouse. Funny how often I pipe existing commandlets, but do not think of making mine that way.

Monday, November 5, 2018

Working with Your Command Line History

A trick I always forget is utilizing command line history, aside from just up-arrow.

Powershell:

This is all from "How To Geek: How to Use Your Command History in Windows PowerShell", which is a very useful site.

I spend a lot of time in Powershell, but also in PS Sessions with other systems. The up-arrow method is not always great when entering/returning from a PS Session, as it will iterate through all recent commands on your native and remote sessions. Get-History will, however, stick to the current session.
PS A:\> Clear-History
PS A:\> Get-History

  Id CommandLine
  -- -----------
 109 Clear-History

PS A:\> Enter-PSSession remotesystem
[remotesystem]: PS C:\Users\johndoe\Documents> Get-History
[remotesystem]: PS C:\Users\johndoe\Documents> cd C:\Temp\
[remotesystem]: PS C:\Temp> Get-History

  Id CommandLine
  -- -----------
   1 Get-History
   2 cd C:\Temp\

[remotesystem]: PS C:\Temp> exit
PS A:\> Get-History

  Id CommandLine
  -- -----------
 109 Clear-History
 110 Get-History
 111 Enter-PSSession remotesystem

In order to use my history (were it lengthy) I would call Invoke-History -Id n. Let's say I wanted to clear my history:
PS A:\> Invoke-History -Id 109
Clear-History
PS A:\> Get-History

  Id CommandLine
  -- -----------
 113 Clear-History

There is other great info at the linked article.

BASH:

BASH is a bit simpler, and maintains history better than Powershell. A good reference is at ss64.com, an invaluable site for the command line. Their article on BASH history is at: Connamd Line History.

The basics are easy. To see your history, use the history command. It will display the commands and their id number:
$ history
    1  history
    2  cd ~
    3  history
    4  touch somefile.txt
    5  history
    6  cd /c/temp
    7  history

To execute history item 4, simply enter !4 and return. If your history is lengthy, you could use [ctrl]+r and then start typing the command you want to find. This will begin to autocomplete the command with the appropriate text. If you want to find the command's id in your history, pipe history through grep.
$ history | grep -i some
    4  touch somefile.txt
    9  touch somefile.txt
   10  history | grep -i some

Friday, November 2, 2018

RegEx Testing Tools

One of the websites I use most is regex101. It is a great place to test your RegEx against text you provide. You can provide sample text, define your expression, then see the substitution results. What's more, you can login and save your expressions for future use. I used this feature in the past for Rexx scripts. We had a file cleanup section in each script. Rather than re-writing the delete line with each file, I would do a Get-ChildItem, and take out the file info, as so. Which leads to another nice feature; if you save an expression, you can share it with a link. Lastly, you can generate code for your expression in javascript, python, php, c#, and several other languages.

It does not, however, do Powershell...

Luckily I found another; regexhero, which is purely for Powershell regular expressions. I have just started using this for a fairly simple -replace line I was writing. regexhero will do -match, -replace, and -split functions where you provide your target string and expression to run against it.

Sadly, regexhero uses Microsoft Silverlight and reportedly only works on Internet Explorer 9+, so Linux users are left out. So too, apparently, are Microsoft Edge users (though it is possible Silverlight is not properly configured on my Edge browser). The web developer in me would like the site to be more universally available, but is also intrigued by the use of Silverlight.

Now, if I could find a way to look at the peculiarities of vi regex...

Thursday, November 1, 2018

Powershell: Get System IP Address

For logging purposes, I needed to get the ethernet ip address for a system. I could parse ipconfig, using regex to find the address based on my subnet, or the simple powershell way with Get-NetIPAddress.

To make things a bit easier, and to avoid issues where your ip address is not what is expected, you could use one of the following:

((Get-NetIPAddress | Where-Object InterfaceAlias -match Ethernet) `
     | Where-Object AddressFamily -Match IPv4).IPAddress

((Get-NetIPAddress | Where-Object InterfaceAlias -match Ethernet) `
     | Where-Object AddressFamily -Match IPv6).IPAddress

((Get-NetIPAddress | Where-Object InterfaceAlias -match Wi-Fi) `
     | Where-Object AddressFamily -Match IPv4).IPAddress

((Get-NetIPAddress | Where-Object InterfaceAlias -match Wi-Fi) `
     | Where-Object AddressFamily -Match IPv6).IPAddress

Obviously, your mileage may vary. For instance, the system I am on now does not obtain IPv6 addresses. My target system does.

How does this work? Get-NetIPAddress returns an object with all of your ip addresses; ethernet, wi-fi, loopback, vpn, vm adapters, etc. You may not be interested in all of these. In my case, I am only interested in the IPv4 ethernet address of a system. Get-NetIPAddress gets me all interfaces. Get-NetIPAddress | Where-Object InterfaceAlias -match Ethernet on my IPv4/IPv6 system gives me:

IPAddress         : aa11::bb22:cc33:dd44:ee55%4
InterfaceIndex    : 4
InterfaceAlias    : Ethernet
AddressFamily     : IPv6
Type              : Unicast
PrefixLength      : 64
PrefixOrigin      : WellKnown
SuffixOrigin      : Link
AddressState      : Preferred
ValidLifetime     : Infinite ([TimeSpan]::MaxValue)
PreferredLifetime : Infinite ([TimeSpan]::MaxValue)
SkipAsSource      : False
PolicyStore       : ActiveStore

IPAddress         : 192.168.0.2
InterfaceIndex    : 4
InterfaceAlias    : Ethernet
AddressFamily     : IPv4
Type              : Unicast
PrefixLength      : 24
PrefixOrigin      : Dhcp
SuffixOrigin      : Dhcp
AddressState      : Preferred
ValidLifetime     : 21:43:14
PreferredLifetime : 21:43:14
SkipAsSource      : False
PolicyStore       : ActiveStore

To break this down and get just the IPv4 entry, we pipe our expression again through a new Where-Object:

(Get-NetIPAddress | Where-Object InterfaceAlias -match Ethernet) `
     | Where-Object AddressFamily -Match IPv4

IPAddress         : 192.168.0.2
InterfaceIndex    : 4
InterfaceAlias    : Ethernet
AddressFamily     : IPv4
Type              : Unicast
PrefixLength      : 24
PrefixOrigin      : Dhcp
SuffixOrigin      : Dhcp
AddressState      : Preferred
ValidLifetime     : 21:43:14
PreferredLifetime : 21:43:14
SkipAsSource      : False
PolicyStore       : ActiveStore

To get just the IPv4 address, enclose the entire expression, and then request the property desired.

((Get-NetIPAddress | Where-Object InterfaceAlias -match Ethernet) `
     | Where-Object AddressFamily -Match IPv4).IPAddress

192.168.0.2

Tuesday, October 30, 2018

Passing a Variable as a ScriptBlock in Powershell

Recently I was tasked with creating a deployment script which would run an executable with an argument. This is performed monthly, and we have just begun deploying it with SCCM. Originally, we would call the executable with its argument, but we find that the executable hangs and many manual fixes must be performed. For some reason, deployment with SCCM seemed to create this hanging executable situation more often than our previous (ancient) distribution method.

Previously, the unscripted deployment would simply call the executable with the argument. I do not want to rewrite my script every month, so my Powershell script should:
  • Accept an argument to pass the executable
  • Run the executable as a Job
  • Monitor the Job, retrying if it takes too long 
The problem was getting $Command = "my.exe /arg x" into a ScriptBlock. Using this syntax we get:

PS A:\> $Command = "my.exe /arg x"
PS A:\> Start-Job -ScriptBlock{ $Command }

Id  Name  PSJobTypeName  State    HasMoreData  Location   Command
--  ----  -------------  -----    -----------  --------   -------
1   Job1  BackgroundJob  Running  True         localhost  $Command

Obviously, our ScriptBlock is not "$Command", so we need to turn it into an acceptable form. To do this we create a ScriptBlock, and then call it into the Job without -ScriptBlock, as follows:

PS A:\> $Command = [scriptblock]::create("my.exe /arg x")
PS A:\> Start-Job $Command

Id  Name  PSJobTypeName  State    HasMoreData  Location   Command
--  ----  -------------  -----    -----------  --------   -------
3   Job3  BackgroundJob  Running  True         localhost  my.exe /arg x

As this is a monthly item, I would not hardcode the $Command with a static argument, I would instead accept an argument into my Powershell script, and then pass it as so (assigning a value for this example):

PS A:\> $arg = "y"
PS A:\> $Command = [scriptblock]::create("my.exe /arg $arg")
PS A:\> Start-Job $Command

Id  Name  PSJobTypeName  State    HasMoreData  Location   Command
--  ----  -------------  -----    -----------  --------   -------
5   Job5  BackgroundJob  Running  True         localhost  my.exe /arg y