$$\newcommand{\eqdef}{\overset{def}{=}}$$

Tahir Hassan's Blog

My Technical Notes

Saturday, 21 January 2017

PowerShell: Propogation of `-Verbose` option and `ShouldProcess` non-handling

The following is an answer I posted on StackOverflow that was about propagation of `-Verbose` and how it ain't respected by `ShouldProcess`.


I was looking to write exactly the same question, and I am writing this almost 7 years later. I am surprised that Microsoft's PowerShell team have not fixed this yet. I have reproduced the issue with PowerShell Version 6 Preview (latest version).

I have come up with a simple workaround, that is, inside the `Inner` function, we create an run a `scriptblock`, setting the `-Verbose` flag by checking `$VerbosePreference` which is correctly set to `Continue`, even though it is not respected by `ShouldProcess`:


Function Outer {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param([string]$Name)
    
    Process {
        Write-Host "Outer called";
        Inner $Name
    }
}

Function Inner {
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
    param([string]$Name)
    
    Process {
        if (-not ($PSBoundParameters.ContainsKey('Verbose'))) {
            $PSBoundParameters.Add('Verbose', [bool]$VerbosePreference -eq 'Continue');
        }
        
        & {
            [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
   
            param([string]$Name)
            
            if ($PSCmdlet.ShouldProcess($Name, "Inner")) {
                Write-Host "Inner called";
            }
        } @PSBoundParameters;
    }
}

Export-ModuleMember *

PowerShell: Overriding `ConfirmPreference`

In PowerShell, if you define a function which `SupportsShouldProcess` and you call it with `-Confirm` switch, it will run the function with `$ConfirmPreference` set to `Low`. Therefore those commands which even have a `Medium` impact (`ConfirmImpact`), such as `Move-Item` will ask for confirmation.

To prevent this, we can call such functions with `-Confirm` set to `$false`:


Move-Item -Path .\Foo.txt -Destination .\Bar.txt -Confirm:$false

Another method of doing this, is to set the `$ConfirmPreference` to `High` (which is the default), and isolate its effects from the rest of the function by using the call operator (`&`) on a script block:


& {
    $ConfirmPreference = 'High';
    Move-Item -Path .\Foo.txt -Destination .\Bar.txt
}

The second approach is well-suited to situations in which we have many function calls for which we want to disable confirmation.

PowerShell `Get-ConfirmImpact` Function

In PowerShell, there is no easy way to find out the `ComfirmImpact` of a command. For that, I have implemented the following function `Get-ConfirmImpact`:


Function Get-ConfirmImpact {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]$Command
    )
    
    Begin {
        $ErrorActionPreference = 'Stop';
    }
    
    Process {
        $commandObj = (Get-Command $Command);
        
        if ($commandObj.CommandType -eq 'Alias') {
            return Get-ConfirmImpact $commandObj.Definition;
        } else {
        
            $attrs = & {
                if ($commandObj.ImplementingType) {
                    $commandObj.ImplementingType.GetCustomAttributes($true);
                } elseif ($commandObj.ScriptBlock) {
                    $commandObj.ScriptBlock.Attributes
                }
            };
            
            $attrs | 
                ? { $_ -is [System.Management.Automation.CmdletCommonMetadataAttribute] } | 
                % { $_.ConfirmImpact };
        }
    }
}

To use it, you pass a command name to the function:


Get-ConfirmImpact Move-Item

which will return `Medium`;

Sunday, 15 January 2017

Compiling Bootstrap 4 on Windows Issue

When compiling Bootstrap 4 on Windows using the following command:


grunt dist

You can potentially get the following error message:


>> fs.js:951
>>   return binding.readdir(pathModule._makeLong(path), options.encoding);
>>                  ^
>> 
>> Error: ENOENT: no such file or directory, scandir 'C:\_code\bootstrap-4.0.0-alpha.6\node_modules\node-sass\vendor'
>>     at Error (native)
>>     at Object.fs.readdirSync (fs.js:951:18)
>>     at Object.getInstalledBinaries (C:\_code\bootstrap-4.0.0-alpha.6\node_modules\node-sass\lib\extensions.js:122:13)
>>     at foundBinariesList (C:\_code\bootstrap-4.0.0-alpha.6\node_modules\node-sass\lib\errors.js:20:15)
>>     at foundBinaries (C:\_code\bootstrap-4.0.0-alpha.6\node_modules\node-sass\lib\errors.js:15:5)
>>     at Object.module.exports.missingBinary (C:\_code\bootstrap-4.0.0-alpha.6\node_modules\node-sass\lib\errors.js:45:5)
>>     at module.exports (C:\_code\bootstrap-4.0.0-alpha.6\node_modules\node-sass\lib\binding.js:15:30)
>>     at Object.<anonymous> (C:\_code\bootstrap-4.0.0-alpha.6\node_modules\node-sass\lib\index.js:14:35)
>>     at Module._compile (module.js:570:32)
>>     at Object.Module._extensions..js (module.js:579:10)

You can fix the issue using:


npm rebuild node-sass

Basic Dialog/Window In AutoHotkey

This example displays a simple GUI using AutoHotkey. It asks for a forename and surname and displays the entered data in a message box.


; Create a new GUI called "PersonGui"
Gui, PersonGui:New
; 1. +AlwaysOnTop - make it always on top so that users can see it
; 2. -SysMenu gets rid of the top-left icon, and the minimize, maximize and close buttons
; 3. +Owner removes the dialog's taskbar icon
Gui, +AlwaysOnTop -SysMenu +Owner 
; Add the forename and surname labels
Gui, Add, Text,, Forename:
Gui, Add, Text,, Surname:
; Add forename and surname textboxes. The ym option starts a new column of controls.
Gui, Add, Edit, vForename ym
Gui, Add, Edit, vSurname
; Add an "OK" button, which, when when pressed, will run the PersonGuiOK label.
; "Default" makes it the default action when Enter key is pressed
Gui, Add, Button, gPersonGuiOK Default, OK
; We want the "Cancel" button to be next to the "OK" button with a margin of 5 pixels
GuiControlGet, OK, Pos
NewXPos := OKX + OKW + 5
; Add the "Cancel" button, running label "PersonGuiCancel", 
; setting its location to be next to the "OK" button, by setting its xy coordinates.
Gui, Add, Button, gPersonGuiCancel x%NewXPos% y%OKY%, Cancel
; Show the dialog, with the title of "Person Details"
Gui, Show,, Person Details
; return ends the auto-execute section. This script is idle until the user does something with the GUI.
return 

 ; PersonGuiOK label is associated with the "OK" button
PersonGuiOK:
; Gui, Submit saves the input from the user to each control's associated variable.
Gui, Submit 
; Show the dialog with the entered Forename and Surname values
MsgBox You entered "%Forename% %Surname%".
; ExitApp terminates the script
ExitApp

; GuiEscape will be run when the user presses the Escape key while the GUI is active 
PersonGuiGuiEscape:
; GuiClose will be run when the dialog is closed.
PersonGuiGuiClose:
; PersonGuiCancel is the label associated with the "Cancel" button
PersonGuiCancel:
; Gui, Destroy: destroy the GUI
Gui, Destroy
ExitApp

If you had more than one GUI in your app, and you wanted to set this this particular GUI `PersonGui` as the default that commands such as `Add` operate on, we use the `Default` sub-command:


Gui, PersonGui:Default

If on the other-hand, you wanted to add a control to a GUI without setting it as the `Default`, you can specify the GUI's name:


Gui, PersonGui:Add, Text,, Forename:

Sunday, 8 January 2017

Finding `max-width` and `max-device-width` programmatically

The following Javascript code finds out the `max-width` and `max-device-width` of a page/device:


(function () {
    function getWidth(type) {
        var startWidth = 100;
        var endWidth = 1000;

        for (var i = startWidth; i <= endWidth; ++i) {
            if (!(window.matchMedia("(min-" + type + ": " + i + "px)").matches)) {
                return i - 1;
            }
        }

        return ">= 1000";
    }

    alert('max-width is ' + getWidth('width') + 'px and max-device-width is ' + getWidth('device-width') + 'px');
})();

Tuesday, 3 January 2017

Querying Results from a stored procedure

To query the result of a stored procedure we first need to execute the stored procedure:


DECLARE @RC int
EXECUTE @RC = [MyDatabase].[dbo].[myStoredProc] 

To store the result of this query into a temporary table, we first need to create the temporary table first and thereafter dump the result of the stored procedure into the new temporary table:


-- creat the temporary table:
CREATE TABLE #tmp(
   [id] int,
   name varchar(64)
   -- other cols here
)

-- now call the 
INSERT INTO #tmp
exec [MyDatabase].[dbo].[myStoredProc] 

A problem with the above method is that we need to write the CREATE TABLE and specify all the columns which might be a laborious task.

An alternative to creating a temp table and writing all the column names is to use the OPENROWSET to run the query:


SELECT tmp.*
FROM OPENROWSET(
    'SQLOLEDB', 
    'Server_name';  -- the server name e.g .\SQLEXPRESS
    'username';     -- the username
    'password',     -- the password
    'EXEC [database_name].[dbo].[stored_procedure_name]' -- statement to execute
) AS tmp

For the server name we can use:


SELECT @@SERVERNAME
Unfortunately, the server needs to be configured for this access and you may not have permissions to change the configuration. The following error message was shown for me:


Msg 15281, Level 16, State 1, Line 1
SQL Server blocked access to STATEMENT 'OpenRowset/OpenDatasource' of component 'Ad Hoc Distributed Queries' because this component is turned off as part of the security configuration for this server. A system administrator can enable the use of 'Ad Hoc Distributed Queries' by using sp_configure. For more information about enabling 'Ad Hoc Distributed Queries', see "Surface Area Configuration" in SQL Server Books Online. 

If we had access to the server we can always use OPENQUERY which is easier and more elegant to use than OPENROWSET but at the same time we need to have admin privileges. To do this we first add a linked server called loopback (link):


EXEC master..sp_addlinkedserver 
    @server = 'loopback',  
    @srvproduct = '',
    @provider = 'SQLNCLI',
    @datasrc = @@SERVERNAME;

EXEC master..sp_serveroption 
    @server = 'loopback', 
    @optname = 'DATA ACCESS',
    @optvalue = 'TRUE';

We can then use OPENQUERY to use loopback to query the same server we are on:


-- do a select only
SELECT * FROM OPENQUERY(loopback, 'EXEC [MyDatabase].[dbo].[myStoredProc];');

-- store the results of the stored procedure into a temporary table
SELECT * INTO #tmp FROM OPENQUERY(loopback, 'EXEC [MyDatabase].[dbo].[myStoredProc];');

Lastly we can use C# to run the stored procedure and then we can determine the column names and column types by interrogating the DataTable and then outputting a CREATE TABLE statement (link):