observeit.psm1

<# observeit PowerShell module by Jonathan Boyko.
TODO:
    Add activity indicator. Some jobs may take a while to complete, such as database upgrades.
    When removing leftovers, deleting folders with recurse
    Ensure the WCF HTTP Activation prerequisite is installed
    Add a switch to make the Application Server work as HTTPS rather than HTTP
    Application Server website seems to start in stopped state
    If installation for a component fails, try again, so that we don't need to ask for more credentials
 
Version 0.7.7 2018-05-21 11:41:03
    - The Install-OIT script now will open a log file for a component installation if the installation fails.
    - Fixed issue where check for the installer version would fail.
Version 0.7.6 2018-05-21 11:26:02
    - The Install-OIT script validates IIS prerequisites prior to installation.
Version 0.7.5 2018-05-10 13:20:58
    The script now retries the installation of a component if the installation fails.
Version 0.7.4 2018-05-08 20:55:20
    Prevent the script from removing the IIS settings for observeit. This just makes a mess.
Version 0.7.3 2018-05-03 13:50:11
    Various minor fixes and improvements:
    The Set-OITEncrypt and Set-OITDebug functions no longer change the encryption state of the Notification Service due to an apparent bug in the configuration file. You will have to alter it manually.
    The script now backs up the current configuration file prior to performing the change. The backed up file is stored in the same directory.
Version 0.7.2 2018-05-02 12:17:08
    After running Install-OIT, the function displays a list of currently installed products. Used to validate that the expected products were indeed installed.
Version 0.7.1 2018-05-02 11:56:42
    The Install-OIT function now checks whether we're installing observeit version 7.5 or higher. If we do, it installs the NodeJS prerequisite.
Version 0.7.0 2018-05-01 15:38:12
    Added the Get-OITFreeSpace function. This function tests specified drive on the server for free space. Either monitor space left in GB or percentage.
Version 0.6.9 2018-04-26 15:29:20
    The Web Console installation procedure now installs the Node.JS prerequisites.
    During removal, the script now cleans up the folders it can and removes IIS settings.
    During installation, the script configures IIS. You can also specify a custom port for the Application Server and Web Console during the installation.
Version 0.6.6 y2018m04d24h15m41s41
    Updated the Install-OIT function for easier setting of GUI level for the installer. Instead of specifying the level via the actual MSIEXEC switch, you can
    specify it via the function's own switch.
Version 0.6.5 y2018m04d17h17m38s06
    Apparently, previous fix did not resolve the issue with Web Categorization module services not stopping. This should work now.
Version 0.6.4 y2018m04d17h14m20s23
    Resolved an issue where Web Categorization module services would not be stopped prior to removal.
 #>

function Test-OITCredentials {
    <#
    .SYNOPSIS
     
    Gets and validates credentials.
    .DESCRIPTION
     
    This function gets credentials from a user, validates the credentials with a domain controller, and then allows use of credentials by subsequent functions.
    #>

    $Global:OITCredential = Get-Credential
    
    Write-Host "Verifying credentials."

    # Source for this function is: http://www.powershellmagazine.com/2013/02/15/pstip-validating-active-directory-user-credentials/
    Add-Type -AssemblyName System.DirectoryServices.AccountManagement
 
    $Global:OITUserName=$($($Global:OITCredential.GetNetworkCredential()).Domain + "\" + $($Global:OITCredential.GetNetworkCredential()).UserName)
    $Global:OITPassword=$($($Global:OITCredential.GetNetworkCredential()).Password)
    
    $ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain
    $pc = New-Object System.DirectoryServices.AccountManagement.PrincipalContext $ct,$Domain
    $ValidationResult = $pc.ValidateCredentials($Global:OITUserName,$Global:OITPassword)

    if ($ValidationResult -eq $false) {
        Write-Host "The supplied credentials cannot be verified. Please try again." -ForegroundColor Red
        Break
    }
}

function Install-OIT {
    <#
    .SYNOPSIS
     
    Installs observeit components.
    .DESCRIPTION
     
    This function installs observeit components. Use this during upgrade scenarios or brand new install scenarios.
    .PARAMETER Global:ApplicationServerPort
     
    Specify the path to the root of the observeit installer. For example: C:\observeit_Setup_vx.x.x.xx\observeit_Setup_vx.x.x.xx\. The script will then look for component installers under the root.
    .PARAMETER Global:DatabaseServer
     
    Specify the FQDN and, if needed, the port to the observeit database server. For example: oit.domain.lab, or oit.domain.lab,2104.
    .PARAMETER InstallDatabase
 
    Specifies whether you would like to install the database. This will either install the database from scratch or upgrade your current database.
    .PARAMETER InstallAppServer
 
    Specifies whether you would like to install the observeit Application Server.
    .PARAMETER InstallWebConsole.
 
    Specified whether you would like to install the observeit Web Console.
    .PARAMETER Global:GUILevel
 
    Specified the MSIEXEC GUI level for your install. Default is passive UI. Specify using MSIEXEC parameters, such as '/qn'
    .EXAMPLE
     
    Install-OIT -InstallerRoot C:\observeit_Setup_v7.4.1.27\observeit_Setup_v7.4.1.27\ -InstallAppServer -Global:DatabaseServer oit.domain.lab -InstallWebConsole -Global:GUILevel "/qn"
    .EXAMPLE
 
    Install-OIT -InstallerRoot C:\observeit_Setup_v7.4.1.27 -InstallAppServer -Global:DatabaseServer oit.domain.lab
    #>

    Param(
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        $InstallerRoot,
        [Parameter(Mandatory=$true,Position=2)]
        [string]
        $DatabaseServer,
        [Parameter(Mandatory=$false,Position=3)]
        [switch]
        $InstallDatabase,
        [Parameter(Mandatory=$false,Position=4)]
        [switch]
        $InstallAppServer,
        [Parameter(Mandatory=$false,Position=5)]
        [switch]
        $InstallWebConsole,
        [Parameter(Mandatory=$false,Position=6)]
        [switch]
        $InstallWebCatModule,
        [Parameter(Mandatory=$false,Position=7)]
        [string]
        $ApplicationServerPort = "4884",
        [Parameter(Mandatory=$false,Position=8)]
        [string]
        $WebConsolePort = "443",
        [switch]
        $GUILevelFull,
        [switch]
        $GUILevelPassive,
        [switch]
        $GUILevelNone
    )

    $Global:InstallerRoot = $InstallerRoot
    $Global:DatabaseServer = $DatabaseServer
    $Global:ApplicationServerPort = $ApplicationServerPort
    $Global:WebConsolePort = $WebConsolePort
    $Global:GUILevelFull = $GUILevelFull
    $Global:GUILevelPassive = $GUILevelPassive
    $Global:GUILevelNone = $GUILevelNone
    $Global:OITModulePath = $(Get-Module observeit).Path

    Test-Elevation
    Test-OITCredentials
    Install-OITPrerequisites

    # Check whether we're installing observeit 7.5 and higher
    # Yes, I know it'd dirty
    $CurrentVersion = $Global:InstallerRoot -match 'v\d.\d.\d.\d{1,3}'
    $CurrentVersion = $Matches[0]
    $CurrentVersion = $CurrentVersion.Replace("v","")
    $CurrentVersion = $CurrentVersion.Split(".")
    if ($CurrentVersion[0] -ge 7) {
        if ($CurrentVersion[1] -gt 4) {
            $InstallNodeJS = $true
        } else {
            $InstallNodeJS = $false
        }
    }

    # If no GUI level specified, assume the Passive mode
    if (!$Global:GUILevelFull -and !$Global:GUILevelPassive -and !$Global:GUILevelNone) {
        Write-Host "No GUI level specified. Assuming Passive." -ForegroundColor Yellow
        $Global:GUILevel = "/qr"
    }

    # Set the GUI level for MSIEXEC based on user preference
    if ($Global:GUILevelFull) {
        $Global:GUILevel = "/qf"
    }

    if ($Global:GUILevelPassive) {
        $Global:GUILevel = "/qr"
    }

    if ($Global:GUILevelNone) {
        $Global:GUILevel = "/qn"
    }

    $DBInstallerList = @{
        "DB" = "$Global:InstallerRoot\DB\SQLPackage.exe"
        "DB_Analytics" = "$Global:InstallerRoot\DB_Analytics\SQLPackage.exe"
    }

    $Global:AppServerInstaller = "$Global:InstallerRoot\Web\AppServer\observeit.AppServerSetup.msi"
    $Global:NodeJSInstaller = "$Global:InstallerRoot\Web\PreRequisite_nodeServices.exe"
    $Global:WebConsoleInstaller = "$Global:InstallerRoot\Web\WebConsole\observeit.WebConsoleSetup.msi"
    $Global:WebCatInstaller = "$Global:InstallerRoot\WebsiteCat\WebsiteCat_Setup.msi"

    if ($InstallDatabase) {
        Write-Host "Installing the databases."

        foreach ($item in $DBInstallerList.Values) {
            Start-Process $item -ArgumentList "/server:$Global:DatabaseServer","/makedatabase","/quiet" -Wait
        }
    }

    # Verify the folders are there
    Write-Host "Verifying directory structure."
    $OITPaths = "$env:ProgramFiles\observeit","$env:ProgramFiles\observeit\Web"
    foreach ($item in $OITPaths) {
        if (!$(Test-Path $item)) {
            Write-Host "Creating directory" $item
            try {
                New-Item $item -ItemType Directory | Out-Null
            }
            catch {
                Write-Host "Failed to create the directory" $item
            }
        }
    }

    # Verify the IIS is there
    Write-Host "Verifying IIS structure."
    Import-Module WebAdministration
    $Global:ApplicationServerPort = ":" + $Global:ApplicationServerPort + ":"
    $Global:WebConsolePort = ":" + $Global:WebConsolePort + ":"
    $Global:ProductsToInstall = @()
    
    if ($InstallAppServer) {
        do {
            #Start-Job -InitializationScript {Import-Module $Global:OITModulePath} -ScriptBlock {Install-OITAppServer}
            Install-OITAppServer
            Find-OITInstalledComponents
            if (!$($Global:InstalledProducts | Where-object {$_.ComponentName -eq "observeit Application Server"})) {
                &"$env:USERPROFILE\appdata\Local\Temp\WebConsole_CA_Log.txt"
                Read-Host "Installation of the observeit Application Server failed. Press Enter key to retry"
            }
        } until ($($Global:InstalledProducts | Where-object {$_.ComponentName -eq "observeit Application Server"}))
    }

    if ($InstallWebConsole) {
        do {
            Install-OITWebConsole
            Find-OITInstalledComponents
            if (!$($Global:InstalledProducts | Where-object {$_.ComponentName -eq "observeit Console"})) {
                &"$env:USERPROFILE\appdata\Local\Temp\AppServer_CA_Log.txt"
                Read-Host "Installation of the observeit Web Console failed. Press Enter key to retry."
            }
        } until ($($Global:InstalledProducts | Where-object {$_.ComponentName -eq "observeit Console"}))
    }
    
    if ($InstallWebCatModule) {
        do {
            Install-OITWebCat
            Find-OITInstalledComponents
            if (!$($Global:InstalledProducts | Where-object {$_.ComponentName -eq "WebsiteCat"})) {
                &"$env:USERPROFILE\appdata\Local\Temp\WebsiteCat_CA_Log.txt"
                Read-Host "Installation of the observeit Web Categorization module failed. Press Enter key to retry."
            }
        } until ($($Global:InstalledProducts | Where-object {$_.ComponentName -eq "WebsiteCat"}))
    }

    Find-OITInstalledComponents
    Write-Host 'Currently installed components:'
    $Global:InstalledProducts

    $Global:OITCredential = $null

}

function Install-OITAppServer {
    $Global:ProductsToInstall += 'observeit Application Server'
    $AppServerAppPool = "IIS:\AppPools\observeitApplication"
    Write-Host "Installing observeit Application Server."
    if (!$(Test-Path $AppServerAppPool)) {
        New-Item $AppServerAppPool | Out-Null
        New-Item IIS:\Sites\observeitApplication -PhysicalPath 'C:\Program Files\observeit\Web\' -Bindings @{protocol="http";bindingInformation="$Global:ApplicationServerPort"} | Out-Null
        Set-ItemProperty IIS:\Sites\observeitApplication\ -Name applicationpool -Value observeitApplication | Out-Null
    }
    $ComponentInstallArguments = "/i",$Global:AppServerInstaller,$Global:GUILevel,"/norestart","DATABASE_SERVER=$Global:DatabaseServer","TARGETAPPPOOL=observeitApplication","TARGETSITE=observeitApplication","DATABASE_LOGON_TYPE=WindowsAccount","SERVICE_USERNAME=$Global:OITUserName","SERVICE_PASSWORD=$Global:OITPassword","/leo","AppServerMSI.log"
    Start-Process msiexec.exe -ArgumentList $ComponentInstallArguments -Wait -NoNewWindow
}

function Install-OITWebConsole {
    $Global:ProductsToInstall += 'observeit Console'
    $WebConsAppPool = "IIS:\AppPools\observeitWebConsole"
    $NodeJSPath = "C:\Program Files (x86)\nodejs"
    Write-Host "Installing observeit Web Console."
    if ($InstallNodeJS -eq $true) {
        if (!$(Test-Path $NodeJSPath)) {
            Write-Host "Installing Web Console prerequisites."
            $ComponentInstallArguments = "/install","/passive","/norestart"
            Start-Process $Global:NodeJSInstaller -ArgumentList $ComponentInstallArguments -Wait
        }
    }
    if (!$(Test-Path $WebConsAppPool)) {
        New-Item $WebConsAppPool | Out-Null
        New-Item IIS:\Sites\observeitWebConsole -PhysicalPath 'C:\Program Files\observeit\Web\' -Bindings @{protocol="https";bindingInformation="$Global:WebConsolePort"} | Out-Null
        Set-ItemProperty IIS:\Sites\observeitWebConsole\ -Name applicationpool -Value observeitWebConsole | Out-Null
    }
    $ComponentInstallArguments = "/i",$Global:WebConsoleInstaller,$Global:GUILevel,"/norestart","DATABASE_SERVER=$Global:DatabaseServer","TARGETAPPPOOL=observeitWebConsole","TARGETSITE=observeitWebConsole","DATABASE_LOGON_TYPE=WindowsAccount","SERVICE_USERNAME=$Global:OITUserName","SERVICE_PASSWORD=$Global:OITPassword","/leo",".\WebConsoleMSI.log"
    Start-Process msiexec.exe -ArgumentList $ComponentInstallArguments -Wait -NoNewWindow
}

function Install-OITWebCat {
    $Global:ProductsToInstall += 'WebsiteCat'
    Write-Host "Installing observeit Website Categorization Module"
    # FIXME: This seems to stop all the services, rather than just the Web Categorization module
    Stop-OITServices -WebsiteCat | Out-Null
    $ComponentInstallArguments = "/i",$Global:WebCatInstaller,$Global:GUILevel,"/norestart","DATABASE_SERVER=$Global:DatabaseServer","DATABASE_LOGON_TYPE=WindowsAccount","SERVICE_USERNAME=$Global:OITUserName","SERVICE_PASSWORD=$Global:OITPassword","/leo",".\WebCatMSI.log"
    Start-Process msiexec.exe -ArgumentList $ComponentInstallArguments -Wait -NoNewWindow
    
}

function Install-OITPrerequisites {
    Write-Host "Ensuring observeit prerequisites and installing as necessary."

    try {
        Install-WindowsFeature Web-Server, Web-WebServer, Web-Common-Http, Web-Default-Doc, Web-Dir-Browsing, Web-Http-Errors, Web-Static-Content, Web-Health, Web-Http-Logging, Web-Performance, Web-Stat-Compression, Web-Security, Web-Filtering, Web-App-Dev, Web-Net-Ext45, Web-Asp, Web-Asp-Net45, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Mgmt-Tools, Web-Mgmt-Console, NET-WCF-Services45, NET-WCF-HTTP-Activation45 â€“IncludeManagementTools -ErrorAction Stop | Out-Null
    }
    catch {
        Write-Host "Failed to install one of prerequisites." -ForegroundColor Red
    }
    try {
        Install-WindowsFeature NET-Framework-45-Core, NET-Framework-45-Features, NET-Framework-45-ASPNET -ErrorAction Stop | Out-Null
    }
    catch {
        Write-Host "Failed to install one of prerequisites." -ForegroundColor Red

    }
}

function Find-OITInstalledComponents {
    <#
    .SYNOPSIS
     
    Looks for observeit components installed on this machine.
    .DESCRIPTION
     
    Performs search in the registry to discover installed observeit components and saves their product IDs in a global variable.
    #>

    $Global:InstalledProducts = @()

    $Products = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ | Get-ItemProperty | Where-Object {$_.Publisher -eq "observeit"}

    foreach ($Product in $Products) {
        $CurrentObject = [PSCustomObject]@{
            ComponentName = $Product.DisplayName
            ProductID = ($Product.UninstallString).Replace("MsiExec.exe /I","").Replace("MsiExec.exe /X","")
        }
        $Global:InstalledProducts += $CurrentObject
    }
}

function Remove-OITInstalledComponents ([switch]$Reboot) {
    <#
    .SYNOPSIS
     
    Uninstalls all observeit componenets.
    .DESCRIPTION
     
    Looks for observeit components installed on the current machine and uninstalls all of them.
    .EXAMPLE
     
    Remove-OITInstalledComponents -Reboot
    #>

    Test-Elevation
    Write-Host "Stopping services:" -ForegroundColor Yellow
    Stop-OITServices -IIS -observeit -WebsiteCat
    Find-OITInstalledComponents
    foreach ($Component in $Global:InstalledProducts) {
        Write-Host "Currently removing" $Component.ComponentName
        Start-Process msiexec.exe -ArgumentList '/x',$Component.ProductID,'/qr' -Wait
    }

    Remove-OITLeftOverItems

    Write-Host "Starting IIS:" -ForegroundColor Yellow
    Start-IIS
    if ($Reboot) {
        Restart-Computer -Force
    }
    if (!$Reboot) {
        Write-Host "Please note reboot may be required to successfully reinstall components." -ForegroundColor Yellow
    }
}

function Remove-OITLeftOverItems {
    <#
    .SYNOPSIS
     
    The synopsis goes here. This can be one line, or many.
    .DESCRIPTION
     
    The description is usually a longer, more detailed explanation of what the script or function does. Take as many lines as you need.
    .PARAMETER computername
     
    Here, the dotted keyword is followed by a single parameter name. Don't precede that with a hyphen. The following lines describe the purpose of the parameter:
    .PARAMETER filePath
     
    Provide a PARAMETER section for each parameter that your script or function accepts.
    .EXAMPLE
     
    There's no need to number your examples.
    .EXAMPLE
    PowerShell will number them for you when it displays your help text to a user.
    #>

    Write-Host "Cleaning up." -ForegroundColor Yellow

    # Look for path to an already-installed observeit install
    try {
        $item = Get-Item "$env:ProgramFiles\observeit" -ErrorAction Stop
    }
    catch {
        Write-Host "No default observeit installation path found." -ForegroundColor Yellow
    }
    if ($item) {
        if ($(Test-Path $item)) {
            Write-Host "Removing directory" $item
            try {
                #Remove-Item $item -Recurse -Force -Confirm:$false | Out-Null
                Get-ChildItem $item -Recurse -File | Remove-Item -Force -Confirm:$false -ErrorAction Stop | Out-Null
                Get-ChildItem $item -Recurse -Directory | Remove-Item -Force -Confirm:$false -ErrorAction Stop | Out-Null
            }
            catch {
                Write-Host "Failed to clean up item" $item -ForegroundColor Red
            }
        }
    } 
}

function Test-Elevation {

    [boolean]$Global:IsElevated = $false

    $WindowsIdentity = [system.security.principal.windowsidentity]::GetCurrent()
    $Principal = New-Object System.Security.Principal.WindowsPrincipal($WindowsIdentity)
    $AdminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator
    if ($Principal.IsInRole($AdminRole)) {
        $Global:IsElevated = $true
    }
    if ($Global:IsElevated -ne $true) {
        Write-Host "Please use elevated PowerShell prompt." -ForegroundColor Yellow
        Break
    }
}

function Find-OITRunningServices {
    <#
    .SYNOPSIS
     
    Discovers running services.
    .DESCRIPTION
     
    Discovers all services related to observeit running on the current machine. This includes IIS and observeit services.
    #>

    $Global:StopIISServicesFilter = '$_.Name -notlike "observeit*" -and $_.Name -notlike "GCF1Service" -and $_.Name -notlike "WebsiteCat.Manager"'
    $Global:StopobserveitServicesFilter = '$_.Name -like "observeit*"'
    $Global:StopWebsiteCatServicesFilter =  '$_.Name -like "GCF1Service" -or $_.Name -like "WebsiteCat.Manager"'
    $Global:StopWebsiteCatProcessFilter = '$_.ProcessName -like "gc*" -or $_.ProcessName -like "WebsiteCat*"' 
    $Global:CurrentServices = @()
    $IISServices = "WMSVC","WAS","W3SVC","MSFTPSVC","IISADMIN","FTPSVC"
    foreach ($Service in $IISServices) {
        try {
            $Service = Get-Service $Service -ErrorAction Stop
            Write-Host "Service" $Service.Name "found."
            $Global:CurrentServices += $Service
        }
        catch {
            Write-Host "Service $Service not found. (That's okay, don't worry about it)" -ForegroundColor Gray
        }
    }
    try {
        $OITServices = Get-Service observeit* -ErrorAction Stop
    }
    catch {
    }
    try {
        $OITServices += Get-Service "GCF1Service","WebsiteCat.Manager" -ErrorAction Stop
    }
    catch {
    }
    foreach ($Service in $OITServices) {
        try {
            $Service = Get-Service $Service.Name -ErrorAction Stop
            Write-Host "Service" $Service.Name "found."
            $Global:CurrentServices += $Service
        }
        catch {
            Write-Host "Service $Service not found." -ForegroundColor Red
        }
    }
}

function Stop-OITServices ([switch]$IIS,[switch]$observeit,[switch]$WebsiteCat) {
    Find-OITRunningServices
    if ($observeit) {
        $CurrentServices = $Global:CurrentServices | Where-Object {Invoke-Expression $Global:StopobserveitServicesFilter}
        foreach ($Service in $CurrentServices) {
            try {
                Stop-Service $Service -Force -ErrorAction Stop
                Write-Host "Successfully stopped" $Service.Name -ForegroundColor Green
            }
            catch {
                Write-Host "Failed to stop service" $Service.Name -ForegroundColor Red
            }
        }
    }
    if ($WebsiteCat) {
        try {
            Get-Process | Where-Object {Invoke-Expression $Global:StopWebsiteCatProcessFilter} | Stop-Process -Force -ErrorAction Stop
        }
        catch {
            Write-Host "Failed to stop Web Categorization module processes." -ForegroundColor Red
        }
        $CurrentServices = $Global:CurrentServices | Where-Object {Invoke-Expression $Global:StopWebsiteCatServicesFilter}
        foreach ($Service in $CurrentServices) {
            try {
                Stop-Service $Service -Force -ErrorAction Stop
                Write-Host "Successfully stopped" $Service.Name -ForegroundColor Green
            }
            catch {
                Write-Host "Failed to stop service" $Service.Name -ForegroundColor Red
            }
        }
    }
    if ($IIS) {
        # TODO: This needs to be updated in the future. Having service names hard-coded is not the best idea.
        $CurrentServices = $Global:CurrentServices | Where-Object {Invoke-Expression $Global:StopIISServicesFilter}
        foreach ($Service in $CurrentServices) {
            try {
                Stop-Service $Service -Force -ErrorAction Stop
                Write-Host "Successfully stopped" $Service.Name -ForegroundColor Green
            }
            catch {
                Write-Host "Failed to stop service" $Service.Name -ForegroundColor Red
            }
        }
    }
}

function Start-IIS {
    $CurrentServices = $Global:CurrentServices | Where-Object {Invoke-Expression $Global:StopIISServicesFilter}
    foreach ($Service in $CurrentServices) {
        try {
            Write-Host "Starting service" $Service.Name
            Start-Service $Service.Name
            if ($(Get-Service $Service.Name).Status -eq "Running") {
                Write-Host "Successfully started service" $Service.Name -ForegroundColor Green
            } else {
                Write-Host "There seems to be an issue starting service" $Service.Name ". Please check service status."
            }
        }
        catch {
            Write-Host "Unable to start service" $Service.Name -ForegroundColor Red
        }
    }
}

function Test-ComputerAddress {
    Param(
        # Parameter help description
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        $InputFile
    )

    # This script expects a CSV file as an input with top row being a value 'Name'. Press any key to continue.
    # Jonathan Boyko, observeit Professional Services, 20180220t185553
    # Initialize the object for data ingestion
    $MachineList = New-Object -TypeName psobject
    $MachineList | Add-Member -Type NoteProperty -Name "ComputerName" -Value ""
    $MachineList | Add-Member -Type NoteProperty -Name "IPAddress" -Value ""
    $MachineList | Add-Member -Type NoteProperty -Name "FirstOctet" -Value ""
    $MachineList | Add-Member -Type NoteProperty -Name "IsAlive" -Value $false

    foreach ($Computer in Get-Content $InputFile)
    {
        #Resolve address
        try
        {
            $ResolveResult = $(Resolve-DnsName -Name $Computer -Type A -ErrorAction Stop).IPaddress
        }
        catch
        {
            $ResolveResult = "Failed"
        }
        #Ping the address
        try
        {
            if ($ResolveResult -ne "Failed")
            {
                $PingResult = Test-NetConnection -ComputerName $ResolveResult -InformationLevel Quiet -ErrorAction Stop -WarningAction SilentlyContinue
            }
        }
        catch
        {
            $PingResult = "Failed"
        }

        $MachineList.ComputerName = $Computer
        $MachineList.IPAddress = $ResolveResult

        if ($MachineList.IPAddress -eq "Failed")
        {
            $PingResult = "Failed"
        }

        $MachineList.FirstOctet = $($MachineList.IPAddress.Split("."))[0]
        $MachineList.IsAlive = $PingResult
        $MachineList
    }
}

function Get-NestedMembership ([string]$SamAccountName) {
    $Identity = Get-ADUser -Identity $SamAccountName
    $Identity = $Identity.SamAccountName
    Import-Module ActiveDirectory
    $Groups = Get-ADPrincipalGroupMembership -Identity $Identity
    $FinalResult = $Groups
    foreach ($1stLevelGroup in $Groups)
    {
        $FinalResult += Get-ADPrincipalGroupMembership -Identity $Group
        foreach ($2ndLevelGroup in $1stLevelGroup)
        {
            $FinalResult += Get-ADPrincipalGroupMembership -Identity $2ndLevelGroup
            foreach ($3rdLevelGroup in $2ndLevelGroup)
            {
                $FinalResult += Get-ADPrincipalGroupMembership -Identity $3rdLevelGroup
                foreach ($4thLevelGroup in $3rdLevelGroup)
                {
                    $FinalResult += Get-ADPrincipalGroupMembership -Identity $4thLevelGroup
                }
            }
        }
    }

    $FinalResult | Sort-Object -Unique
}

function Start-Stopwatch
    {
        $Global:StartTime = Get-Date
    }

function Stop-Stopwatch
    {
        $StopTime = Get-Date
        $FinalTime = $StopTime-$Global:StartTime
        Write-Host $FinalTime.Hours "h" $FinalTime.Minutes "m" $FinalTime.Seconds "s"
    }

function Test-OITHeartBeatLatency {
        <#
        .SYNOPSIS
            Tests latency to the observeit application server.
        .DESCRIPTION
            Uses the heartbeat sensor on the observeit application server to test connection latency between the client and the application server.
        .PARAMETER HeartBeatHost
            Hostname you would like to poll. The script will append the necessary string to query the Application Server status.
        .PARAMETER LongPollInterval
            What value, in milliseconds, would be considered too long/too high when polling the server.
        .PARAMETER ExpectedResult
            The function will stop execution when the expected result changes. For example, if we expect the application server to be online, the expected result would be 1. However, if we know that application server is down, the expected result will be 0.
        .PARAMETER OutputPath
            Output the result into a CSV file.
        .EXAMPLE
            Test-HeartBeatLatency -HeartBeatHost "https://oit.domain.lab:443/observeitapplicationserver/HeartBeat.asmx/IsAlive" -LongPollInterval 1000 -ExpectedResult 1 -OutputPath "c:\output.csv"
        #>

        Param(
            # URL we want to poll
            [Parameter(Mandatory=$true,Position=1)]
            [String[]]
            $HeartBeatHost,
            # How much is too long when polling the remote heartbeat. You can specify multiple URLs.
            [Parameter(Mandatory=$true,Position=2)]
            [int]
            $LongPollInterval,
            # Are we waiting for the Application Server to come back or go down?
            [Parameter(Mandatory=$true,Position=3)]
            [string]
            $ExpectedResult,
            [Parameter(Mandatory=$false,Position=4)]
            [string]
            $OutputPath
        )

        add-type @"
        using System.Net;
        using System.Security.Cryptography.X509Certificates;
        public class TrustAllCertsPolicy : ICertificatePolicy {
            public bool CheckValidationResult(
                ServicePoint srvPoint, X509Certificate certificate,
                WebRequest request, int certificateProblem) {
                return true;
            }
        }
"@

        $AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
        [System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
        [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy

        $AverageArray = @()
        $CurrentAverage = 0

        if (!$HeartBeatHost)
            {
                Write-Host "The URL is empty." -ForegroundColor Red
                Return
            }
        
        if ($ExpectedResult -eq "1")
            {
                $ExpectedResult = '*<string xmlns="http://observeit.WebServices/HeartBeat.asmx/">1</string>*'
            }
        
        if ($ExpectedResult -eq "0")
            {
                $ExpectedResult = '*<string xmlns="http://observeit.WebServices/HeartBeat.asmx/">0</string>*'
            }
        
        $CurrentOutput = New-Object psobject
        $CurrentOutput | Add-Member -Type NoteProperty -Name "PolledHost" -Value ""
        $CurrentOutput | Add-Member -Type NoteProperty -Name "PollDate" -Value ""
        $CurrentOutput | Add-Member -Type NoteProperty -Name "PollTime" -Value ""
        $CurrentOutput | Add-Member -Type NoteProperty -Name "CurrentLatency" -Value ""
        $CurrentOutput | Add-Member -Type NoteProperty -Name "AverageLatency" -Value ""
        $CurrentOutput | Add-Member -Type NoteProperty -Name "Exc" -Value ""
        $CurrentOutput | Add-Member -Type NoteProperty -Name "ExcCnt" -Value 0
        $CurrentOutput | Add-Member -Type NoteProperty -Name "TotalPolls" -Value 0
        $CurrentOutput | Add-Member -Type NoteProperty -Name "ExcRatio" -Value 0

        Write-Host "Begin polling"
        Do
            {
                $CurrentOutput.TotalPolls++
                foreach ($HBURL in $HeartBeatHost)
                    {
                        $ExecutionTime = Measure-Command -Expression {$Alive = Invoke-WebRequest -Uri $HBURL}
                        $ExecutionTimeMS = $ExecutionTime.TotalMilliseconds
                        $ExecutionTimeMS = [Math]::Round($ExecutionTimeMS, 0)
                        
                        $AverageArray += $ExecutionTimeMS
                        $CurrentAverage = ($AverageArray | Measure-Object -Average).Average
                        $CurrentAverage = [Math]::Round($CurrentAverage, 0)
                        
                        $CurrentOutput.Exc = ""
                        $CurrentOutput.PolledHost = $HBURL.Replace("observeitapplicationserver/HeartBeat.asmx/IsAlive","")
                        $CurrentOutput.PollDate = Get-Date -Format yyyy-MM-dd
                        $CurrentOutput.PollTime = Get-Date -Format hh:mm:ss
                        $CurrentOutput.CurrentLatency = $ExecutionTimeMS
                        $CurrentOutput.AverageLatency = $CurrentAverage
                        $CurrentOutput.ExcRatio = $CurrentOutput.ExcCnt / $CurrentOutput.TotalPolls
                        $CurrentOutput.ExcRatio = [Math]::Round($CurrentOutput.ExcRatio,2)
                        if ($ExecutionTimeMS -ge $LongPollInterval) {
                                $CurrentOutput.Exc = "X"
                                $CurrentOutput.ExcCnt++
                            }
                    $CurrentOutput
                    if ($OutputPath) {
                        $CurrentOutput | Export-Csv $OutputPath -Append -NoTypeInformation
                    }
                    Start-Sleep -Seconds 5
                        }
                }
        
        While ($Alive.Content -like $ExpectedResult)   
    }

function Get-OITLinks ([switch]$AllLanguages) {
            <#
        .SYNOPSIS
            Retrieves observeit release download links.
        .DESCRIPTION
            This function parses the observeit download page for links to releases.
        .PARAMETER AllLanguages
            Specify this parameter to get links for all languages, not just the default English version.
        .EXAMPLE
            Get-OITLinks -AllLanguages
        #>

        $OITURI = "https://www.observeit.com/support/product_releases_download/"
        $RegEx = "http:\/\/.*observeit.*\d.zip"
        if ($AllLanguages)
            {
                $RegEx = "http:\/\/.*observeit.*.zip"
            }
        $URIs = (Invoke-WebRequest -Uri $OITURI).links | Where-Object {$_.href -like "*observeit_Setup*" -and $_.href -match $RegEx}
        $URIs.href
        $URIs.href | Select-Object -First 1 | Set-Clipboard
    }
function Find-StringInFile {
        Param(
            [Parameter(Mandatory=$true,Position=1)]
                [String]$Path = "D:\Temp\Cases",

            [Parameter(Mandatory=$false,Position=2)]
                [String]$Keyword = "exception"
            )

        $CurrentPath = $Path + "\*.*"
        Get-Content -Path $CurrentPath | Select-String -Pattern $Keyword
    }

function Find-OITPaths {
        <#
        .SYNOPSIS
            Generates observeit paths.
        .DESCRIPTION
            This function generates paths for observeit Application Server(s) configuration files and saves the paths to a global variable. Useless on its own, it's designed to be a helper function for other functions.
        .EXAMPLE
            Find-OITPaths
        #>

        # Define variables, including full paths to the required config files as per documentation.
        $ProgramFiles = ${env:ProgramFiles}
        $OITInstallPath = $ProgramFiles + "\observeit\"
        $OITBackendConfigFiles = "web\observeitApplicationServer\web.config","web\observeit\web.config","RuleEngineService\bin\ActivityAlerts.Service.exe.config","HealthMonitor\bin\observeit.HealthMonitor.Service.exe.config"#,"NotificationService\observeit.WinService.exe.config"
        $OITAgentConfigFiles = "observeitAgent\Bin\bcplc.exe.config","observeitAgent\Bin\dlmonitor.exe.config","observeitAgent\Bin\rcdact.exe.config","observeitAgent\Bin\rcdcl.exe.config","observeitAgent\Bin\rcdsvc.exe.config","observeitAgent\Bin\svchostw.exe.config","observeitAgent\Bin\svcwtch.exe.config"
        $Global:OITBackendConfigFullPath = @(1..$OITBackendConfigFiles.Length)
        $Global:OITAgentConfigFullPath = @(1..$OITAgentConfigFiles.Length)

        # We're using the ArrayPosition variable during a loop to go through the list of install files and set full path for them
        $ArrayPosition = 0

        # Start calculating full paths
        do
            {
                # Write the full paths into a global variable accessible from other functions
                $Global:OITBackendConfigFullPath.Item($ArrayPosition) = $OITInstallPath + $OITBackendConfigFiles.Item($ArrayPosition)
                $ArrayPosition = $ArrayPosition + 1
            }
        while ($ArrayPosition -lt $OITBackendConfigFiles.Length)

}
function Start-OITConfigBackup {
    # This function just backs up configuration files
    $CurrentDateString = Get-Date -Format yyyymmddhhssffff
    foreach ($OITConfigFile in $Global:OITBackendConfigFullPath) {
        $BackupFileName = $OITConfigFile + ".$CurrentDateString"
    Copy-Item -Path $OITConfigFile -Destination $BackupFileName -Force
    }
}
function Restart-OITServices {
    <#
    .SYNOPSIS
        Restarts observeit services on the given machine
    #>

        $OITServices = Get-Service observeit*
        if ($OITServices)
            {
                Write-Host "Restarting observeit services..." -ForegroundColor Yellow
                try
                    {
                        Get-Service observeit* | Restart-Service -Force -ErrorAction Stop
                        Write-Host "Done!"
                    }
                catch
                    {
                        Write-Host "Failed!"
                    }
            }
        if (!$OITServices)
            {
                Write-Host "No observeit services found!" -ForegroundColor Red
            }
    }

function Restart-IIS {
    <#
    .SYNOPSIS
        Restarts IIS services on the given machine.
    #>

        $IISServices = Get-Service AppHostSVC,FTPSVC,IISADMIN,MSFTPSVC,W3SVC,WAS,WMSVC -ErrorAction SilentlyContinue -WarningAction SilentlyContinue        
        if ($IISServices)
            {
                Write-Host "Restarting IIS services..." -ForegroundColor Yellow
                try
                    {
                        $IISServices | Restart-Service -Force -ErrorAction Stop
                        Write-Host "Done!" -ForegroundColor Yellow
                    }
                catch
                    {
                        Write-Host "Failed!" -ForegroundColor Red
                    }
            }
        if (!$IISServices)
            {
                Write-Host "No IIS services found!" -ForegroundColor Red
            }
    }

#### This block is used to monitor file system free space
function Get-OITFreeSpace {
    <#
    .SYNOPSIS
     
    Checks for free space on the specified drive.
    .DESCRIPTION
     
    Will test the specified drive for amount of space and will produce a status in according to specification. It will then send an alert email to specified recipient via the specified mail host. It does not currently support SMTP authentication.
    .PARAMETER Volume
     
    Specify the drive letter of the drive to check.
    .PARAMETER MonitorPercent
     
    Specifies you want to check for percent of free space left on the drive.
    .PARAMETER MonitorGB
     
    Specifies you want to check for amount of GBs left on the drive.
    .PARAMETER LevelWarning
     
    Enter a number denoting percentage or amount of GB that would produce a warning. When the level of GBs or percent free goes below this number, the function will produce a warning status.
    .PARAMETER LevelCritical
     
    Enter a number denoting percentage or amount of GB that would produce a critical alert. When the level of GBs or percent free goes below this number, the function will produce a critical status.
    .PARAMETER Recipient
     
    Specifies recipient of the email to be sent with the alert.
    .PARAMETER MailFrom
     
    Specifies email of the sender.
    .PARAMETER MailHost
     
    Specified mail host to be used to send an email.
    .EXAMPLE
     
    Get-OITFreeSpace -Volume i: -MonitorPercent -LevelWarning 15 -LevelCritical 10 -Recipient ciso@domain.lab -MailFrom observeit@domain.lab -MailHost oit.domain.lab
    .EXAMPLE
    Get-OITFreeSpace -Volume i: -MonitorGB -LevelWarning 100 -LevelCritical 50 -Recipient ciso@domain.lab -MailFrom observeit@domain.lab -MailHost oit.domain.lab
    #>


    Param(
        # Volume letter
        [Parameter(Mandatory=$true)]
        [string]
        $Volume,
        # Declares whether you want to monitor CalculatedValue or size
        [Parameter(Mandatory=$false)]
        [switch]
        $MonitorPercent,
        # Declares whether you want to monitor amount of free GBs
        [Parameter(Mandatory=$false)]
        [switch]
        $MonitorGB,
        # Declare the monitoring threshold for Warning level
        [Parameter(Mandatory=$true)]
        [int]
        $LevelWarning,
        # Declare the monitoring threshold for Critical level
        [Parameter(Mandatory=$true)]
        [int]
        $LevelCritical,
        # Recipient of the email alert
        [Parameter(Mandatory=$true)]
        [string]
        $Recipient,
        # Sender
        [Parameter(Mandatory=$true)]
        [string]
        $MailFrom,
        # Address of the mail host
        [Parameter(Mandatory=$true)]
        [string]
        $MailHost
    )

    $Volume = $Volume.Replace(":","")
    $Volume = $Volume.ToUpper()

    $CurrentDrive = Get-Volume -DriveLetter $Volume
    $CurrentHostName = $env:COMPUTERNAME

    if ($MonitorPercent) {
        $CalculatedValue = ($CurrentDrive.SizeRemaining / $CurrentDrive.Size) * 100
        $CalculatedValue = [math]::Round($CalculatedValue)
    }

    if ($MonitorGB) {
        $CalculatedValue = $CurrentDrive.SizeRemaining
        $CalculatedValue = $CalculatedValue /1Gb
        $CalculatedValue = [math]::Round($CalculatedValue)
    }

    if ($CalculatedValue -gt $LevelWarning) {
        $CurrentLevel = "OK"
    } elseif ($CalculatedValue -le $LevelCritical) {
        $CurrentLevel = "Critical"
    } elseif ($CalculatedValue -le $LevelWarning) {
        $CurrentLevel = "Warning"
    }

    if ($MonitorPercent) {
        $FreeSpaceCheckResult = "The drive $Volume is in $CurrentLevel state. There's $CalculatedValue % of free space."
    }

    if ($MonitorGB) {
        $FreeSpaceCheckResult = "The drive $Volume is in $CurrentLevel state. There's $CalculatedValue GB of free space."
    }

    $FreeSpaceCheckSubject = "$CurrentHostName file system at $CurrentLevel level."
    $FreeSpaceCheckResult
    
    if ($CurrentLevel -eq "Warning" -or $CurrentLevel -eq "Critical") {
        Send-MailMessage -To $Recipient -From $MailFrom -Body $FreeSpaceCheckResult -Subject $FreeSpaceCheckSubject -SmtpServer $MailHost
    }
}
function Set-OITEncrypt {
    <#
    .SYNOPSIS
        Enables observeit encryption.
    .DESCRIPTION
        This function edits observeit configuration files in order to enable traffic encryption between observeit Application Server(s) and remote SQL server.
    .PARAMETER $EnableEncryption
        Has possible values of $true or $false, where $true enables the encryption and $false disables it. This parameter is False by default.
    .EXAMPLE
        Set-OITEncrypt -EnableEncryption $true
    #>

        # Define parameters. Basically, you have to say whether you want the encryption on or off
        Param(
            [Parameter(Mandatory=$true,Position=1)]
                [boolean]$EnableEncryption = $false
            )
        
        # Run the function generating full paths
        Find-OITPaths
        Start-OITConfigBackup

        # Now, let's get to actually enabling/disabling encryption.
        # For each config file...
        foreach ($OITConfigFile in $Global:OITBackendConfigFullPath)
            {
                if ($OITConfigFile)
                    {
                        Write-Host "Nothing to work on." -ForegroundColor Red
                    }
                Write-host  $oitconfigfile
                Write-Host " "
                Write-Host "-----------------------------------------------------------"
                Write-Host " "
                Write-Host "Current config file is:" $OITConfigFile -ForegroundColor Gray
                Write-Host "Current string in file is:" -ForegroundColor Green
        
                # ...find the string for ConnectionStrings and present what's currently in the file to the user.
                Select-String -Path $OITConfigFile -Pattern '<add name="ConnectionString" connectionString="Data Source=' -CaseSensitive -SimpleMatch

                # Next, if user requested to disable encryption, let the user know that's what we're doing and disable it for the current file.
                If ($EnableEncryption -eq $false)
                    {
                        Write-Host " "
                        Write-Host "Disabling encryption for file" $OITConfigFile -ForegroundColor Red
                        Write-Host " "
                        (Get-Content $OITConfigFile).Replace(";Encrypt=True","") | Out-File $OITConfigFile -Force
                        # TODO: This here should be another parameter.
                        (Get-Content $OITConfigFile).Replace(";TrustServerCertificate=True","") | Out-File $OITConfigFile -Force
                    }
        
                # If, however, we're enabling encryption, enable it for the current file and let the user know.
                if ($EnableEncryption -eq $true)
                    {
                        Write-Host " "
                        Write-Host "Enabling encryption for file" $OITConfigFile -ForegroundColor DarkGreen
                        Write-Host " "
                        (Get-Content $OITConfigFile).Replace("Integrated Security=SSPI","Integrated Security=SSPI;Encrypt=True") | Out-File $OITConfigFile -Force
                    }
        
                # Finally, show the newly-applied string to the user.
                Write-Host "New string in file is:" -ForegroundColor Green
                Select-String -Path $OITConfigFile -Pattern '<add name="ConnectionString" connectionString="Data Source=' -CaseSensitive -SimpleMatch
            }

        Write-Host "Encryption is currently set to" $EnableEncryption -ForegroundColor Yellow
        Write-Host "Please also remember this script does not alter the encryption state of the Notification Service." -ForegroundColor Yellow
    }
    

function Set-OITDebug {
        <#
    .SYNOPSIS
        Enables or disables observeit component debugging.
    .DESCRIPTION
        This function edits observeit configuration files to enable observeit Application Server(s) components debugging.
    .PARAMETER $EnableDebug
        Enables or disables debugging. Possible values are $true or $false
    .PARAMETER $LogLevel
        Sets the debugging log level. Default level is 3. Level 4 if the full debug logging, but produces very large files.
    .PARAMETER $Restartobserveit
        Mandatory. Specifies whether to restart observeit components to enable debugging.
    .PARAMETER $Restartobserveit
        Set to $true to restart IIS services as well.
    .EXAMPLE
        Set-OITDebug -EnableDebug $true -LogLevel 4
    #>

        Param(
            [Parameter(Mandatory=$false,Position=1)]
            [boolean]$EnableDebug=$false,

            [Parameter(Mandatory=$false,Position=2)]
            [int]$LogLevel=3,

            [Parameter(Mandatory=$false,Position=3)]
            [switch]$Restartobserveit,

            [Parameter(Mandatory=$false,Position=4)]
            [switch]$RestartIIS
        )

        # Run the function generating full paths
        Find-OITPaths
        Start-OITConfigBackup

        # Now, let's get to actually enabling/disabling encryption.
        # In each config file...
        foreach ($OITConfigFile in $Global:OITBackendConfigFullPath)
            {
                Write-Host "###############################################"
                Write-Host " "
                Write-Host " "
                Write-Host "Current config file is:" $OITConfigFile -ForegroundColor Gray
                Write-Host "Current string in file is:" -ForegroundColor Green
        
                # ...find the string for ConnectionStrings and present what's currently in the file to the user.
                Select-String -Path $OITConfigFile -Pattern '<add name="General" value="' -CaseSensitive -SimpleMatch

                # Next, if we were asked to enable debugging, do so.
                if ($EnableDebug -eq $true)
                    {
                        if ($LogLevel -eq 3)
                            {
                                Write-Host "Enabling debugging for file" $OITConfigFile -ForegroundColor Red
                                (Get-Content $OITConfigFile).Replace('<add name="General" value="1" />','<add name="General" value="3" />') | Set-Content $OITConfigFile -Force
                                Write-Host " "
                            }
                        if ($LogLevel -eq 4)
                            {
                                Write-Host "Enabling debugging for file" $OITConfigFile -ForegroundColor Red
                                Write-Host " "
                                Write-Host "Please remember this log level generates large trace files!" -ForegroundColor Red
                                (Get-Content $OITConfigFile).Replace('<add name="General" value="1" />','<add name="General" value="4" />') | Set-Content $OITConfigFile -Force
                                Write-Host " "
                            }
                    }

                # If requested to disable debug, proceed to look for all possible debug values and disable them.
                if ($EnableDebug -eq $false)
                    {
                        Write-Host "Disabling debugging for file" $OITConfigFile -ForegroundColor Green
                        (Get-Content $OITConfigFile).Replace('<add name="General" value="4" />','<add name="General" value="1" />') | Set-Content $OITConfigFile -Force
                        (Get-Content $OITConfigFile).Replace('<add name="General" value="3" />','<add name="General" value="1" />') | Set-Content $OITConfigFile -Force
                        (Get-Content $OITConfigFile).Replace('<add name="General" value="2" />','<add name="General" value="1" />') | Set-Content $OITConfigFile -Force
                        Write-Host " "
                    }
                
                # Finally, write final string.
                Write-Host "New string in file is: " -ForegroundColor Green
                Select-String -Path $OITConfigFile -Pattern '<add name="General" value="' -CaseSensitive -SimpleMatch
                Write-Host " "
            }

        Write-Host "Processing done!" -ForegroundColor Yellow

        if ($Restartobserveit)
            {
                Restart-OITServices
            }
        if (!$Restartobserveit)
            {
                Write-Host "Please remember to restart observeit services to enable debugging!" -ForegroundColor Yellow
                Write-Host "Please also remember this script does not alter the debug level of the Notification Service." -ForegroundColor Yellow
            }
        if ($RestartIIS)
            {
                Restart-IIS
            }
    }

function Start-OITAppSrvParser {
        <#
    .SYNOPSIS
        Separates strings into objects.
    .DESCRIPTION
        This function splits observeit log strings into objects.
    .PARAMETER Path
        Specify path where textual log files are located. Cannot be empty.
    .PARAMETER Keyword
        Specifies which textual expression we are looking for.
    .PARAMETER FileType
        Specifies log file type we are looking for.
    .EXAMPLE
        Start-LogParserSeparator -Path C:\Logs -Keyword "available" -FileType "*.log"
    #>

        Param(
            [Parameter(Mandatory=$False,Position=1)]
            [String]$Path="C:\Program Files (x86)\observeit\observeitAgent",

            [Parameter(Mandatory=$False,Position=2)]
            [string]$Keyword,

            [Parameter(Mandatory=$False,Position=3)]
            [string]$FileType = "*.*",

            [Parameter(Mandatory=$False,Position=4)]
            [boolean]$QuickSearch = $true
        )
        # The $FinalArray variable will be available globally, so other functions may use it as well.
        $global:FinalArray = @()
        $DateRegex = "\d{1,4}-\d{1,2}-\d{1,2}"
        $TimeRegex = "\d{1,2}:\d{1,2}:\d{1,2}.\d{1,3}"
        $ThreadRegex = "ThreadId: \d{1,3}"
        $EventTypeRegex = "\[.\]"
        $MessageRegex = "\d{1,4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}.\d{1,3} ThreadId: \d{1,3} \[.\]\s"

        # Generate the path to the log files.
        $FullPath = $Path + "\" +$FileType

        # If $Keyword variable is not empty...
        if ($Keyword)
            {
                # ...get contents of the log files. Get only the strings that match the pattern.
                Write-Host "Doing first data pass"
                $Data = Get-Content -Path $FullPath | Select-String -Pattern $DateRegex
                Write-Host "Doing second data pass"
                $Data = Get-Content -Path $FullPath | Select-String -Pattern $Keyword
            }

        # If, however, $Keyword variable is empty...
        if (!($Keyword))
            {
                # ... get the data without any filters.
                Write-Host "Doing first data pass"
                $Data = Get-Content -Path $FullPath | Where-Object {$_ -match $DateRegex}
            }

# BEGIN FOREACH LOOP
        # We will examine each string from the files we have read.
        Write-Host "Sorting data"
        foreach ($String in $Data)
            {
                # Split each into an object
                $CurrentObject = New-Object psobject
                $CurrentObject | Add-Member -Type NoteProperty -Name "Date" -Value $($String | Select-String -Pattern $DateRegex).Matches.Value
                $CurrentObject | Add-Member -Type NoteProperty -Name "Time" -Value $($String | Select-String -Pattern $TimeRegex).Matches.Value
                $CurrentObject | Add-Member -Type NoteProperty -Name "ThreadID" -Value $($String | Select-String -Pattern $ThreadRegex).Matches.Value
                $CurrentObject | Add-Member -Type NoteProperty -Name "EventType" -Value $($String | Select-String -Pattern $EventTypeRegex).Matches.Value
                
                # Leave only the message
                $String = $String -replace $DateRegex,""
                $String = $String -replace $TimeRegex,""
                $String = $String -replace $ThreadRegex,""
                $String = $String -replace $EventTypeRegex,""

                $CurrentObject | Add-Member -Type NoteProperty -Name "Message" -Value $String.Substring(3)

                $global:FinalArray += $CurrentObject
            }
        $global:FinalArray | Sort-Object -Property Date,Time
        }