Tuesday, July 21, 2020

Stupid Citrix Trick #5: Updating INI files

Problem: To configure some older programs we have to manipulate INI files. There's not a built-in way to do this in our preferred scripting language, Powershell.

Solution: Like so many things in IT, we must begin with a story about Lotus Notes.

For those not familiar with Notes, it's an email and workflow program, and its workflow capabilities are especially useful.  You can use it to set up a change request system, for example, with tickets being routed to various users for their approval.  For the time it was considered advanced.

So about ten years ago we had just upgraded Notes from version 7 to version 8.  The new version came with a redesigned graphical user interface which was much easier to use, but it still included the old GUI for users who preferred it.  Crucially, each interface had a different set of licenses, so if your account was licensed for the old interface, you needed to run that interface to stay in compliance.  Which interface the application launched with was configured by a command-line parameter, and since we had separate Active Directory groups for the users of each interface, it was simple to set up two Citrix published apps, each one assigned to the appropriate group and with the proper command-line settings.  And all was right with the world.

Then we changed our email system to Microsoft Exchange.  Users would not launch Notes to get their email as before; instead their workflow notifications would go to their Outlook inbox.  When they received one they'd just click a link in the message and it would launch Notes and take them directly to the right document.

And this was a huge problem! No longer could I use command-line parameters to configure the Notes interface.  Due to the limited number of licenses I couldn't just always use one or the other.  I had to find another way.

Oh, and just to add a dash of urgency, our senior executives were going on a retreat in a couple of weeks where they would only be able to access Outlook and Notes via Citrix, so I had to get this working ASAP.

But then, hallelujah! I found that you could use an INI setting in the Notes configuration file for each user that would configure the interface.  This INI file was stored in a known location in the user's home folder, so we could use a launch script for Outlook that would make sure that the INI file had the correct settings before launching, and when the user clicked a link to a Notes document, Notes would already be set up.

Too bad there's no way to change INI settings in Powershell.

Well, no native way.

Fortunately Powershell is the Swiss army knife of programming languages, and if something can be done, it can be done in Powershell, and in this case what we need to do is to use some functions from kernel.dll.

(Quick note: I am not an expert at this, and likely found it on the Internet. I'd like to give credit but this was at least eight years ago.)

For our purposes we'll pretend we're working with a file called "sample.ini", which contains this text:

[Section1]
Key1=FirstKey
Key2=SecondKey

[Section2]
Key3=ThirdKey

Here's the first function, Get-IniSetting, which reads from an INI file.  You can see the parameters are all required, and used to specify the file name, the section we want to work with, and the key we want to fetch.  The first and last are simple but for the section you'll want to use the name without the brackets. To get a key from [Section2] you'd just pass "Section2" to this function.


Function Get-IniSetting
{

[CmdletBinding()]
Param
(
    [Parameter(Mandatory = $True)]
    [string]$File,

    [Parameter(Mandatory = $True)]
    [string]$Section,

    [Parameter(Mandatory = $True)]
    [string]$Key
)

$Signature = @’
[DllImport("kernel32.dll")]
public static extern uint GetPrivateProfileString(
    string lpAppName,
    string lpKeyName,
    string lpDefault,
    StringBuilder lpReturnedString,
    uint nSize,
    string lpFileName);
‘@

    $type = Add-Type -MemberDefinition $Signature -Name Win32Utils -Namespace GetPrivateProfileString -Using System.Text -PassThru

    $builder = New-Object System.Text.StringBuilder 1024

    $type::GetPrivateProfileString($Section, $Key, "", $builder, $builder.Capacity, $File) | Out-Null
   
    $builder.ToString()

}


Here's what a call to that function would look like. In this case the result would be the string value "SecondKey":

Get-IniSetting -File "C:\Users\Graham\Documents\sample.ini" -Section "Section1" -Key "Key2"

Next up is the function to write settings to an INI file.  If you look at the parameters you'll see that it has a parameter set, so based on the parameters passed it can do a couple of different things.


Function Write-IniSetting
{

[CmdletBinding()]
Param
(
    [Parameter(Mandatory = $True)]
    [string]$File,

    [Parameter(Mandatory = $True)]
    [string]$Section,

    [Parameter(ParameterSetName = "SetKey")]
    [string]$Key,

    [Parameter(ParameterSetName = "SetKey")]
    [string]$Value
)

$Signature = @’
[DllImport("kernel32.dll")]
public static extern bool WritePrivateProfileString(
    string lpAppName,
    string lpKeyName,
    string lpString,
    string lpFileName);
‘@


    Switch ($PSCmdlet.ParameterSetName)
    {
        "SetKey"
            {
                If ($Value -ne (Get-IniSetting -File $File -Section $Section -Key $Key))
                {
                    $type = Add-Type -MemberDefinition $Signature -Name Win32Utils -Namespace WritePrivateProfileString -Using System.Text -PassThru

                    $type::WritePrivateProfileString($Section, $Key, $Value, $File) | Out-Null
                }
            }
        default
            {
                $type = Add-Type -MemberDefinition $Signature -Name Win32Utils -Namespace WritePrivateProfileString -Using System.Text -PassThru

                $type::WritePrivateProfileString($Section, [NullString]::Value, [NullString]::Value, $File) | Out-Null
            }
    }
}

This function does two different things based on what parameters are passed.  The first two, $File and $Section, are exactly the same as before, but the $Key and $Value parameters are optional.  If all four are specified then the function works as you'd expect, setting the value of the specified section and key, and creating it if it doesn't already exist.  Here's a what that call would look like:

Write-IniSetting -File "C:\Users\Graham\Documents\sample.ini" -Section "Section2" -Key "Key2" -Value "AnotherValue" 


But if you only include the $File and $Section parameters, it does something much different: it deletes the entire section. This can come in handy if you need to make wholesale changes.  This function call would delete Section2 and all the data it contains:

Write-IniSetting -File "C:\Users\Graham\Documents\sample.ini" -Section "Section2"

This is really a Stupid Powershell Trick more than a Stupid Citrix Trick - something that will come up many times - but it's extremely useful when dealing with older applications, all of which seem to live out their golden years on the Citrix farm.

No comments:

Post a Comment