MarqMetrix-InstrumentManagement.psm1

$global:__MarqMetrixSpectroscopyInstrumentCurrentConnection = $null

function Connect-InstrumentClient
(
    [Parameter(ParameterSetName="ShortCode", Mandatory=$true)] [string] $RemoteHost, 
    [Parameter(ParameterSetName="ShortCode")][int] $Port = 80, 
    [Parameter(ParameterSetName="ShortCode")][switch] $UseHttps,
    [Parameter(ParameterSetName="ConnectionString")][string] $ConnectionString
)
{
    if ($ConnectionString)
    {
        $values = ConvertFrom-StringData -StringData $ConnectionString.Replace(";", "`n")
        $hostUri = New-Object 'System.Uri' ([string]$values["Uri"])
        $connection = New-Connection -Hostname $hostUri.Host -Port $hostUri.Port -ApiKey $values["SharedKey"]
        return $connection
    }
    else
    {
        if ($UseHttps)
        {
            $request = Request-ShortCode -RemoteHost $RemoteHost -Port $Port -UseHttps
        }
        else
        {
            $request = Request-ShortCode -RemoteHost $RemoteHost -Port $Port
        }
        $shortCode = Read-Host -Prompt "Short code"
        return New-Connection -ShortCodeRequest $request -ShortCode $shortCode
    }
}

function Request-ShortCode
(
    [Parameter(Mandatory=$true)] [string] $RemoteHost, 
    [int] $Port = 80, 
    [switch] $UseHttps
)
{
    $schema = "http"
    if ($UseHttps){
        $schema = "https"
    }
    $uri = "${schema}://${RemoteHost}:${Port}/api/Security/GenerateShortCode"
    $response = Invoke-WebRequest -Uri $uri -Method Get
    $responseObj = ConvertFrom-Json -InputObject $response
    $result = New-Object PSObject -Property @{ 
        GenerationId = $responseObj.generationId;
        Hostname = $RemoteHost;
        Port = $Port;
        UseHttps = $UseHttps 
    }

    return $result
}

function New-Connection
(
    [Parameter(Mandatory=$true, Position=0, ParameterSetName='ShortCodeConnection')]
    [PSObject] $ShortCodeRequest,
    [Parameter(Mandatory=$true, Position=1, ParameterSetName='ShortCodeConnection')]
    [string] $ShortCode,
    [Parameter(Position=0, ParameterSetName='StandardConnection')]
    [string] $Hostname,
    [Parameter(Position=1, ParameterSetName='StandardConnection')]
    [int] $Port = 80,
    [Parameter(Mandatory=$true, Position=2, ParameterSetName='StandardConnection')]
    [string] $ApiKey,
    [Parameter(Position=3, ParameterSetName='StandardConnection')]
    [switch] $UseHttps
)
{
    if ($ShortCodeRequest -ne $null)
    {
        $Hostname = $ShortCodeRequest.Hostname
        $Port = $ShortCodeRequest.Port
        $UseHttps = $ShortCodeRequest.UseHttps
    }

    $schema = "http"
    if ($UseHttps)
    {
        $schema = "https"
    }

    if ($ShortCodeRequest -ne $null)
    {
        $uri = "${schema}://${hostname}:${port}/api/Security/AccessKey"
        $requestBody = @{ type = "Primary"; "generationId" = $ShortCodeRequest.GenerationId; "shortCode" = $ShortCode }
        $response = Invoke-WebRequest -Uri $uri -Method Post -Body (ConvertTo-Json $requestBody) -ContentType "application/json"
        $responseObj = ConvertFrom-Json -InputObject $response
        $ApiKey = $responseObj.key
    }

    $result = @{
        Hostname = $Hostname;
        Port = $Port;
        UseHttps = $UseHttps;
        PrimaryApiKey = $ApiKey;
        PrimaryApiKeyHmac = New-Object System.Security.Cryptography.HMACSHA256 -Property @{ Key = ([System.Convert]::FromBase64String($ApiKey)) };
        SecondaryApiKey = "";
        SecondaryApiKeyHmac = New-Object System.Security.Cryptography.HMACSHA256;
        InstrumentId = $null
    };

    $apiKeyResult = Get-ApiKey -Connection $result -Type Secondary

    $result.SecondaryApiKey = $apiKeyResult.key
    $result.SecondaryApiKeyHmac.Key = [System.Convert]::FromBase64String($apiKeyResult.key)
    
    $instruments = Get-Instruments -Connection $result

    $result.InstrumentId = $instruments.items[0].id

    $calibrations = Get-Calibrations -Connection $result
    $result.Calibrations = $calibrations.items

    Set-Connection $result

    $result
}

function Get-ConnectionString(
    [PSObject] $Connection
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ($Connection.UseHttps)
    {
        $scheme = "https"
    }
    else
    {
    $scheme = "http"
    }

    return "Uri=$($scheme)://$($Connection.Hostname):$($Connection.Port);SharedKey=$($Connection.PrimaryApiKey)"
}

function Set-Connection(
    [Parameter(Mandatory=$true)][PSObject] $Connection
    )
{
    $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection = $Connection
}

function Set-Instrument([PSObject] $Connection, [Parameter(Mandatory=$true)] [string] $InstrumentId)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $Connection.InstrumentId = $InstrumentId
}

function Invoke-InstrumentWebRequest([PSObject] $Connection, [string] $RelativeUrl, [string] $QueryString, [string] $Method, [object] $Body, [string] $ContentType)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $hostname = $Connection.Hostname
    $port = $Connection.Port
    $schema = "http"
    if ($Connection.UseHttps)
    {
        $schema = "https"
    }
    $RelativeUrl = $RelativeUrl.TrimStart('/')

    $uri = "${schema}://${hostname}:${port}/${RelativeUrl}"
    $now = (Get-Date).ToUniversalTime()

    $encryptText = "$($Method.ToUpper())`n$RelativeUrl`n$ContentType`n$($now.ToString("R"))"
    $encryptMessage = [System.Convert]::ToBase64String($Connection.PrimaryApiKeyHmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($encryptText)))

    $headers = @{
        Authorization = "SharedKey $encryptMessage"
        Date = $now.ToString("R")
    }
    
    $response = Invoke-WebRequest -Uri $uri -Method $Method -Body $Body -ContentType $ContentType -Headers $headers

    return $response
}

function Get-ApiKey([PSObject] $Connection, [ValidateSet("Primary", "Secondary")][string] $Type = "Primary")
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Security/AccessKey" -Method "Post" -ContentType "application/json" -Body (ConvertTo-Json @{ type = $Type; })
    return ConvertFrom-Json $response.Content
}

function Get-Instruments([PSObject] $Connection)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments" -Method "Get"
    return ConvertFrom-Json $response.Content
}

function Set-LaserEnabled([PSObject] $Connection, [string] $InstrumentId = $null, [switch] $Disable)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }
    $request = @{
        isLaserEnabled = !$Disable
    }
    $_ = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$($InstrumentId)" -Method Put -Body (ConvertTo-Json $request) -ContentType "application/json"
}

function Get-Instrument(
    [PSObject] $Connection,
    [string] $InstrumentId = $null)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId" -Method Get
    return ConvertFrom-Json $response.Content
}

function Set-DisplayName(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [Parameter(Mandatory=$true)][string] $Value
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }
    $request = @{
        displayName = $Value
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId" -Method Put -Body = (ConvertTo-Json $request) -ContentType "application/json"
    return ConvertFrom-Json $response.Content
}

function Get-Calibrations(
    [PSObject] $Connection,
    [string] $InstrumentId = $null
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Calibration" -Method Get

    $calibrations = ConvertFrom-Json $response.Content

    $Connection.Calibrations = $calibrations.value

    return $calibrations
}

function Get-Calibration(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [string] $CalibrationId = $null
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Calibration/$CalibrationId" -Method Get
    return ConvertFrom-Json $response.Content    
}

function Add-Calibration(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [string] $Json = $null
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Calibration/Data" -Method Post -Body $Json -ContentType "application/json"

    return ConvertFrom-Json $response.Content    
}

function Remove-Calibration(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [Parameter(Mandatory=$true)][string] $CalibrationId
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Calibration/$CalibrationId" -Method Delete
}

function Get-Samples(
    [PSObject] $Connection,
    [string] $InstrumentId = $null
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Samples" -Method Get
    return ConvertFrom-Json $response.Content
}

function Get-Sample(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [Parameter(Mandatory=$true)][string] $SampleId
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Samples/$SampleId" -Method Get
    return ConvertFrom-Json $response.Content
}

function Get-SampleData(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [Parameter(Mandatory=$true)][string] $SampleId
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Samples/$SampleId/Data" -Method Get
    return ConvertFrom-Json $response.Content
}

function Stop-SampleAcquisition(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [Parameter(Mandatory=$true)][string] $SampleId
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Samples/$SampleId/Cancel" -Method Get
    return ConvertFrom-Json $response.Content
}

function Invoke-AcquireSample(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [Parameter(Mandatory=$true)][int] $IntegrationTime,
    [Parameter(Mandatory=$true)][int] $LaserPower,
    [int] $SampleAverageCount = 1,
    [switch] $AutoDark,
    [switch] $Wait,
    [Parameter(ParameterSetName = "ComputeParameters")][string] $SPC = $null,
    [Parameter(ParameterSetName = "ComputeParameters")][string] $CSV = $null,
    [Parameter(ParameterSetName = "ComputeParameters")][switch] $Append,
    [Parameter(ParameterSetName = "ComputeParameters")][ValidateSet("Linear", "RamanShift", "Wavelength")][string] $AxisAlignment = "RamanShift",
    [Parameter(ParameterSetName = "ComputeParameters")][ValidateSet("Raw", "DarkSubtraction", "IntensityCorrection")][string] $ProcessingType = "IntensityCorrection"
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ([string]::IsNullOrEmpty($InstrumentId))
    {
        $InstrumentId = $Connection.InstrumentId
    }

    $metadata = @{};
    $darkSampleInfo = $null;

    if ($AutoDark)
    {
        $darkRequest = @{
            integrationTime = [System.TimeSpan]::FromMilliseconds($IntegrationTime).ToString();
            laserPower = 0;
            sampleAverageCount = $SampleAverageCount;
        };

        $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/AcquireSample" -Method Post -Body (ConvertTo-Json $darkRequest) -ContentType "application/json"

        $darkSampleInfo = ConvertFrom-Json $response.Content

        $metadata["DarkSampleId"] = $darkSampleInfo.id
    }

    
    $request = @{
        integrationTime = [System.TimeSpan]::FromMilliseconds($IntegrationTime).ToString();
        laserPower = $LaserPower;
        sampleAverageCount = $SampleAverageCount;
        metadata = $metadata;
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/AcquireSample" -Method Post -Body (ConvertTo-Json $request) -ContentType "application/json"
    $sampleInfo = ConvertFrom-Json $response.Content
    
    if ($Wait)
    {
        $sleepTime = $IntegrationTime
        do
        {
            Start-Sleep -Milliseconds $sleepTime

            $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Instruments/$InstrumentId/Samples/$($sampleInfo.id)" -Method Get
            $sampleInfo = ConvertFrom-Json $response.Content

            $sleepTime = 500
        } until ($sampleInfo.status -eq "Completed" -or $sampleInfo.status -eq "Cancelled" -or $sampleInfo.status -eq "Faulted")
    }

    if (![string]::IsNullOrEmpty($SPC) -or ![string]::IsNullOrEmpty($CSV))
    {
        $computedSample = Invoke-ComputeSample -Connection $Connection -InstrumentId $InstrumentId -SampleInfo $sampleInfo -AxisAlignment $AxisAlignment -ProcessingType $ProcessingType -SPC:$SPC -CSV:$CSV -Append:$Append
    }
    
    return $sampleInfo
}

function Invoke-ComputeSample(
    [PSObject] $Connection,
    [string] $InstrumentId = $null,
    [Parameter(Mandatory=$true)][PSObject] $SampleInfo,
    [ValidateSet("Linear", "RamanShift", "Wavelength")][string] $AxisAlignment = "RamanShift",
    [ValidateSet("Raw", "DarkSubtraction", "IntensityCorrection")][string] $ProcessingType = "IntensityCorrection",
    [string] $SPC = $null,
    [string] $CSV = $null,
    [Parameter(ParameterSetName = "ComputeParameters")][switch] $Append
)
{
    $calibrationInfo = $Connection.Calibrations[0]
        
    if ($Connection.CalibrationData -eq $null)
    {
        $Connection.CalibrationData = @{}
    }

    if ($Connection.CalibrationData[$InstrumentId] -eq $null)
    {
        $Connection.CalibrationData[$InstrumentId] = @{}
        $Connection.CalibrationData[$InstrumentId][$calibrationInfo.id] = Get-Calibration -Connection $Connection -InstrumentId $InstrumentId -CalibrationId $calibrationInfo.id
    }

    $calibration = $Connection.CalibrationData[$InstrumentId][$calibrationInfo.id]

    $sample = Get-SampleData -Connection $Connection -InstrumentId $InstrumentId -SampleId $SampleInfo.id

    $yData = ConvertTo-UInt16Array -Base64Data $sample.data.buffer

    if ($ProcessingType -eq "DarkSubtraction" -or $ProcessingType -eq "IntensityCorrection")
    {
        $darkSampleId = $SampleInfo.options.metadata.DarkSampleId
        $darkSample = Get-SampleData -Connection $Connection -InstrumentId $InstrumentId -SampleId $darkSampleId

        $darkYData = ConvertTo-UInt16Array -Base64Data $darkSample.data.buffer

        for ($i = 0; $i -lt $yData.Length; $i++)
        {
            $yData[$i] = $yData[$i] - $darkYData[$i]
        }

        if ($ProcessingType -eq "IntensityCorrection")
        {
            for ($i = 0; $i -lt $yData.Length; $i++)
            {
                $yData[$i] = $yData[$i] * $calibration.intensityCorrection[$i]
            }
        }
    }

    [System.Double[]] $xData = $null

    if ($AxisAlignment -eq "Linear")
    {
        $xData = New-Object 'System.Double[]' $yData.Length
        for ($i = 0; $i -lt $xData.Length; $i++)
        {
            $xData[$i] = $i
        }
    }
    elseif ($AxisAlignment -eq "RamanShift")
    {
        $xData = CalculateRamanShift -Size $yData.Length -ReferenceWavelength $calibration.laserWavelength -Arguments $calibration.arguments
    }
    elseif ($AxisAlignment -eq "Wavelength")
    {
        $xData = CalculateWavelengths -RamanShift (CalculateRamanShift -Size $yData.Length -ReferenceWavelength $calibration.laserWavelength -Arguments $calibration.arguments) -LaserWavelength $calibration.laserWavelength
    }

    $result = {
        Sample = $sample;
        DarkSample = $darkSample;
        XData = $xData;
        YData = $yData;
    }

    if (![string]::IsNullOrEmpty($SPC))
    {
        $fileStream = [System.IO.File]::Create($SPC)

        SpcWriteStreamData -stream $fileStream -samplesDataX $xData -samplesDataY $yData -sampleTakenDate ([System.DateTime]::UtcNow) -logData ""

        $fileStream.Close()
        $fileStream.Dispose()
    }

    if (![string]::IsNullOrEmpty($CSV))
    {
        
        if (!$Append)
        {
            Set-Content -Path $CSV -Value "$([string]::Join(",", $xData))"
        }
        Add-Content -Path $CSV -Value "$([string]::Join(",", $yData))"
    }

    return $result;
}

function Get-NetworkLocations(
    [PSObject] $Connection
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "odata/NetworkLocations" -Method Get
    return (ConvertFrom-Json $response.Content).value
}

function Add-NetworkLocation(
    [PSObject] $Connection,
    [Parameter(Mandatory=$true)][string] $Path,
    [Parameter(Mandatory=$true)][string] $Username,
    [Parameter(Mandatory=$true)][string] $Password
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $request = @{
        path = $Path;
        credentials = @{
            username = $Username;
            password = $Password;
        }
    };

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "odata/NetworkLocations" -Method Post -Body (ConvertTo-Json $request) -ContentType "application/json"
    return ConvertFrom-Json $response.Content
}

function Remove-NetworkLocation(
    [PSObject] $Connection,
    [Parameter(Mandatory=$true)][string] $NetworkLocationId
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "odata/NetworkLocations/$NetworkLocationId" -Method Delete
}

function Update-NetworkLocationConnectivity(
    [PSObject] $Connection,
    [Parameter(Mandatory=$true)][string] $NetworkLocationId
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "odata/NetworkLocations/$NetworkLocationId/RefreshConnectivity" -Method Post
    return ConvertFrom-Json $response.Content
}

function Get-DefaultNetworkExportSettings(
    [PSObject] $Connection
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "odata/NetworkLocations/DefaultExportSettings" -Method Get

    if ($response.StatusCode -eq 204) {
        return $null
    }
    
    return ConvertFrom-Json $response.Content
}


function Set-DefaultNetworkExportSettings(
    [PSObject] $Connection,
    [ValidateSet("Linear", "RamanShift", "Wavelength")][string] $OutputAxisAlignment = "RamanShift",
    [ValidateSet("Raw", "DarkSubtraction", "IntensityCorrection")][string] $OutputProcessingType = "IntensityCorrection",
    [ValidateSet("MarqMetrixSampleJson", "SPC")][string] $OutputFileFormat = "SPC",
    [Parameter(Mandatory=$true)][string] $FilenameScheme,
    [Parameter(Mandatory=$true)][string] $NetworkLocationId
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $request = @{
        outputAxisAlignment = $OutputAxisAlignment;
        outputProcessingType = $OutputProcessingType;
        outputFileFormat = $OutputFileFormat;
        fileNameScheme = $FilenameScheme;
        networkLocationId = $NetworkLocationId;
    };

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "odata/NetworkLocations/SetDefaultExportSettings" -Method Post -Body (ConvertTo-Json $request) -ContentType "application/json"

    return ConvertFrom-Json $response.Content
}


function Remove-DefaultNetworkExportSettings(
    [PSObject] $Connection
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "odata/NetworkLocations/DeleteDefaultExportSettings" -Method Post
}

function Get-LoggingConfiguration(
    [PSObject] $Connection
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Logs/GetConfiguration" -Method Get

    return ConvertFrom-Json $response.Content
}

function Set-LoggingConfiguration(
    [PSObject] $Connection,
    [Parameter(Mandatory=$true)][ValidateSet("Trace", "Debug", "Information", "Warning", "Error", "Critical", "None")][string] $LogLevel
)
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    $levels = @{ Trace = 0; Debug = 1; Information = 2; Warning = 3; Error = 4; Critical = 5; None = 6 }

    $request = @{
        logLevel = $levels[$LogLevel];
    };

    $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Logs/SetConfiguration" -Method Post -Body (ConvertTo-Json $request) -ContentType "application/json"
}

function Get-Logs(
    [PSObject] $Connection,
    [switch] $Raw
)
{
if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    if ($Raw)
    {
        $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Logs/Raw" -Method Get
        return $response.Content
    }
    else
    {
        $response = Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Logs" -Method Get
        return ConvertFrom-Json $response.Content
    }
}

function Clear-Logs()
{
    if ($Connection -eq $null)
    {
        $Connection = $global:__MarqMetrixSpectroscopyInstrumentCurrentConnection
    }

    Invoke-InstrumentWebRequest -Connection $Connection -RelativeUrl "api/Logs/Clear" -Method Post
}

## Pass the data property from the deserialized json into this call
function ConvertTo-UInt16Array([PSObject] $Base64Data)
{
    $buffer = [System.Convert]::FromBase64String($Base64Data)
    $result = New-Object 'System.UInt16[]' ($buffer.Length / 2)
    [System.Buffer]::BlockCopy($buffer, 0, $result, 0, $buffer.Length)
    return $result
}


function CalculateRamanShift(
    [Parameter(Mandatory=$true)][int] $Size,
    [Parameter(Mandatory=$true)][double] $ReferenceWavelength,
    [Parameter(Mandatory=$true)][double[]] $Arguments
)
{
    $result = New-Object 'System.Double[]' $Size

    for ($i = 0; $i -lt $Size; $i++)
    {
        $wavelength = [double]0.0
        for ($j = $Arguments.Length - 1; $j -gt -1; $j--)
        {
            $wavelength = $wavelength * $i + $Arguments[$j];
        }

        $result[$i] = ((1 / $referenceWavelength) - (1 / $wavelength)) * 10000000;
    }

    return $result;
}

function CalculateWavelengths(
    [Parameter(Mandatory=$true)][double[]] $RamanShift,
    [Parameter(Mandatory=$true)][double] $LaserWavelength
)
{
    $result = New-Object 'System.Double[]' $RamanShift.Length

    $laserWavelengthCentimeters = 1e-7 * $laserWavelength

    for ($i = 0; $i -lt $RamanShift.Length; $i++)
    {
        $result[$i] = $laserWavelength / (1 - $ramanShift[$i] * $laserWavelengthCentimeters)
    }

    return $result;
}

function SpcWriteStreamData(
    [System.IO.Stream] $stream,
    [double[]] $samplesDataX,
    [double[]] $samplesDataY,
    [DateTime] $sampleTakenDate,
    [string] $logData,
    [string] $spcHeaderText = "MarqMetrix",
    [bool] $unevenlySpaced = $true,
    [bool] $isProcessed = $true,
    [bool] $multifile = $false
)
{
    $dataWriter = New-Object 'System.IO.BinaryWriter' ($stream, ([System.Text.Encoding]::UTF8), $true)

    if ($unevenlySpaced) # storage format uneven is mostly used for Raman shift, even for raw spectra
    {
        $min = [System.Linq.Enumerable]::Min([System.Collections.Generic.IEnumerable[double]]$samplesDataX)
        $max = [System.Linq.Enumerable]::Max([System.Collections.Generic.IEnumerable[double]]$samplesDataX)
        SpcWriteHeader -dataWriter $dataWriter -firstRamanShift $min -lastRamanShift $max -spectralPoints $samplesDataX.Length -fdate $sampleTakenDate -description $spcHeaderText
    }
    else
    {
        SpcWriteHeader -dataWriter $dataWriter -firstRamanShift 0 -lastRamanShift ($samplesDataY.Length - 1) -spectralPoints $samplesDataX.Length -fdate $sampleTakenDate -description $spcHeaderText
    }

    # if unevenly spaced - write the x values
    if ($unevenlySpaced) # write x data for raman shift
    {
        SpcWriteData -dataWriter $dataWriter -data $samplesDataX
    }

    SpcWriteSubHeader -dataWriter $dataWriter -unevenlySpaced $unevenlySpaced

    # write subfile Y data if unevenly spaced or data if evenly spaced
    SpcWriteData -dataWriter $dataWriter -data $samplesDataY

    # write the log data
    if ($logData.Length > 0)
    {
        SpcWriteLogDataBlock -dataWriter $dataWriter -logCnt $logData.Length
        $dataWriter.Write($logData)
    }

    # write flush data
    $dataWriter.Flush()
    $dataWriter.Close()
    $dataWriter.Dispose()
}

function SpcWriteHeader(
    [System.IO.BinaryWriter] $dataWriter,
    [double] $firstRamanShift,
    [double] $lastRamanShift,
    [int] $spectralPoints,
    [DateTime] $fdate, 
    [string] $description, 
    [bool] $unevenlySpaced = $true
)
{
    $uneven = [byte]0x00
    if ($unevenlySpaced)
    {
        $uneven = [byte]0x80
    }
    $dataWriter.Write([byte]$uneven); # uneven 0x80, 0 = evenly spaced
    $dataWriter.Write([byte]0x4B);      # new SPC format
    $dataWriter.Write([byte]0x0B);      #Raman spectrum
    $dataWriter.Write([byte]0x80);      # stored as floats
    $dataWriter.Write([System.UInt32]$spectralPoints);      # spectral points
    $dataWriter.Write($firstRamanShift);  # First Raman shift
    $dataWriter.Write($lastRamanShift);   # Last Raman shift
    $dataWriter.Write([System.UInt32]1);         # number of sub files
    $dataWriter.Write([byte]13);        # x axis type Raman shift
    $dataWriter.Write([byte]4);         # y axis type counts
    $dataWriter.Write([byte]0);         # not used
    $dataWriter.Write([byte]0);         # not used
    $dataWriter.Write((SpcConvertDateTime -dateTimeNow $fdate)); # Write file creation date-time

    $blankdata = New-Object 'System.Byte[]' 9;

    $dataWriter.Write($blankdata);        # do not specify resolution
    $dataWriter.Write($blankdata);        # 9 characters is not enough to give a meaningful description of the instrument. Do not use
    $dataWriter.Write([System.UInt16]0);       # NA
    $floatNull = New-Object 'System.Single[]' 8;
    $floatNull | ForEach-Object { $dataWriter.Write($_) } #fspare
    
    # write experiment name, etc 130 char
    $descriptionArray = New-Object 'System.Char[]' 130;
    if ($description.Length -gt 130)
    {
        $description = $description.Substring(0, 130);
    }
    $charArray = $description.ToCharArray();
    $charArray.CopyTo($descriptionArray, 0);
    $dataWriter.Write($descriptionArray);

    $dataWriter.Write((New-Object 'System.Char[]' 30)) # fcatxt

    # 0x4220 or 0x2220
    $spectralPointsSize = $spectralPoints * 4 + 0x220;
    if ($unevenlySpaced)
    {
        $spectralPointSize = $spectralPoints * 8 + 0x220
    }

    $dataWriter.Write([System.UInt32]$spectralPointsSize); # data dependent LogData offset
                                                # dataWriter.Write((uint)(unevenlySpaced ? 0x4220 : 0x2220)); // data dependent LogData offset
                                                # dataWriter.Write((UInt32)(4 * 2048 ) + 512 +32 + 64); // data dependent LogData offset
    $dataWriter.Write([System.UInt32]0);         # fmods
    $dataWriter.Write([byte]0);          
    $dataWriter.Write([byte]0);          
    $dataWriter.Write([System.UInt16]0);       # fsampin
    $dataWriter.Write([float]0);        # fsampin
    $dataWriter.Write((New-Object 'System.Char[]' 48))     # fsampin
    $dataWriter.Write([float]0);        # fzinc
    $dataWriter.Write([System.UInt32]0);         # fwplanes
    $dataWriter.Write([float]0);        # fwinc
    $dataWriter.Write([byte]0);         # fwtype
    $dataWriter.Write((New-Object 'System.Char[]' 187));    # freserv
}

function SpcWriteSubHeader(
    [System.IO.BinaryWriter] $dataWriter,
    [bool] $unevenlySpaced
)
{
    $subexp = [byte]0x00
    if ($unevenlySpaced)
    {
        $subexp = [byte]0x80
    }
    $dataWriter.Write([byte]0x00);      # subflgs
    $dataWriter.Write([byte]$subexp);   # subexp
    $dataWriter.Write([System.UInt16]0);       # subindx
    $dataWriter.Write([float]0);        # subtime
    $dataWriter.Write([float]0);        # subnext
    $dataWriter.Write([float]0);        # subnois
    $dataWriter.Write([System.UInt32]0);         # subnpts
    $dataWriter.Write([System.UInt32]0);         # subscan
    $dataWriter.Write([float]0);        # subwlevel
    $dataWriter.Write((New-Object 'System.Char[]' 4));     # subresv
}

function SpcWriteData(
    [System.IO.BinaryWriter] $dataWriter,
    [double[]] $data
)
{
    $data | ForEach-Object { $dataWriter.Write([System.Convert]::ToSingle($_)) }
}

function SpcWriteLogDataBlock(
    [System.IO.BinaryWriter] $dataWriter,
    [int] $logCnt)
{
    $logsizd = 64 + $logCnt;
    $logsizma = $logsizd / 4096;
    $logsizmaMod = $logsizd % 4096;
    $logsizm = $logsizma * 4096;
    if ($logsizmaMod -gt 0)
    {
        $logsizm += 4096;
    }
    $dataWriter.Write([System.UInt32]$logsizd);   # logsizd byte size of log block and header
    $dataWriter.Write([System.UInt32]$logsizm);   # logsizm byte size for memory rounded up to 4096
    $dataWriter.Write([System.UInt32]64);         # logxto byte offset from beginnng of log block
    $dataWriter.Write([System.UInt32]0);          # logbins private binary bloc
    $dataWriter.Write([System.UInt32]0);          # logdsk fixed
    $dataWriter.Write((New-Object 'System.Char' 42))# logspar
}

function SpcConvertDateTime(
    [DateTime] $dateTimeNow
)
{
    $dateTimeSpc = [System.UInt32]0;
    
    $dateTimeSpc = $dateTimeSpc -bor [System.UInt32]($dateTimeNow.Year -band 0xFFF);
    $dateTimeSpc = $dateTimeSpc -shl 4;
    $dateTimeSpc = $dateTimeSpc -bor [System.UInt32]($dateTimeNow.Month -band 0xF);
    $dateTimeSpc = $dateTimeSpc -shl 5;
    $dateTimeSpc = $dateTimeSpc -bor [System.UInt32]($dateTimeNow.Day -band 0x1F);
    $dateTimeSpc = $dateTimeSpc -shl 5;
    $dateTimeSpc = $dateTimeSpc -bor [System.UInt32]($dateTimeNow.Hour -band 0x1F);
    $dateTimeSpc = $dateTimeSpc -shl 6;
    $dateTimeSpc = $dateTimeSpc -bor [System.UInt32]($dateTimeNow.Minute -band 0x3F);

    return $dateTimeSpc;
}

Export-ModuleMember -Function Connect-InstrumentClient
Export-ModuleMember -Function Request-ShortCode
Export-ModuleMember -Function New-Connection
Export-ModuleMember -Function Get-ConnectionString
Export-ModuleMember -Function Set-Connection
Export-ModuleMember -Function Set-Instrument
Export-ModuleMember -Function Get-ApiKey
Export-ModuleMember -Function Get-Instruments
Export-ModuleMember -Function Set-LaserEnabled
Export-ModuleMember -Function Get-Instrument
Export-ModuleMember -Function Set-DisplayName
Export-ModuleMember -Function Get-Calibrations
Export-ModuleMember -Function Get-Calibration
Export-ModuleMember -Function Add-Calibration
Export-ModuleMember -Function Remove-Calibration
Export-ModuleMember -Function Get-Samples
Export-ModuleMember -Function Get-Sample
Export-ModuleMember -Function Get-SampleData
Export-ModuleMember -Function Stop-SampleAcquisition
Export-ModuleMember -Function Invoke-AcquireSample
Export-ModuleMember -Function Invoke-ComputeSample
Export-ModuleMember -Function Get-NetworkLocations
Export-ModuleMember -Function Add-NetworkLocation
Export-ModuleMember -Function Remove-NetworkLocation
Export-ModuleMember -Function Update-NetworkLocationConnectivity
Export-ModuleMember -Function Get-DefaultNetworkExportSettings
Export-ModuleMember -Function Set-DefaultNetworkExportSettings
Export-ModuleMember -Function Remove-DefaultNetworkExportSettings
Export-ModuleMember -Function Get-LoggingConfiguration
Export-ModuleMember -Function Set-LoggingConfiguration
Export-ModuleMember -Function Get-Logs
Export-ModuleMember -Function Clear-Logs