observeit.psm1

<# observeit PowerShell module by Jonathan Boyko.
 #>

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."
 
    $Global:OITUserName=$($($Global:OITCredential.GetNetworkCredential()).Domain + "\" + $($Global:OITCredential.GetNetworkCredential()).UserName)
    $Global:OITPassword=$($($Global:OITCredential.GetNetworkCredential()).Password)
    
    try {
        $CredentialsTest = Start-Process -FilePath cmd.exe /c -PassThru -Credential $Global:OITCredential -ErrorAction Stop
        Write-Host "Credential test succeeded." -ForegroundColor Green
        $Global:ValidationResult = $true
    }
    catch {
        Write-Host "Credentials test failed." -ForegroundColor Red
    }
}

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

    do {
        Test-OITCredentials
        if ($Global:ValidationResult -eq $false) {
            $UserReply = Read-Host "The supplied credentials cannot be verified. Would you like to retry? (Otherwise continue) Y/N"
            if ($UserReply -eq "N") {
                $Global:ValidationResult = $true
            }
        }
    } while ($Global:ValidationResult -eq $false)

    Write-Host "Currently running in context of " -NoNewline
    Write-Host "$env:USERNAME" -ForegroundColor Yellow

    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:SQLNCLIInstaller = "$Global:InstallerRoot\Web\sqlncli-2012-64-QFE.msi"
    $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 {
            Install-OITAppServer
            Find-OITInstalledComponents
            if (!$($Global:InstalledProducts | Where-object {$_.ComponentName -eq "observeit Application Server"})) {
                &"$env:USERPROFILE\appdata\Local\Temp\AppServer_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"
    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
        }
    }
    
    Write-Host "Installing the SQL Native Client."
    $ComponentInstallArguments = "/i",$Global:SQLNCLIInstaller,$Global:GUILevel,"/norestart","IAcceptMSODBCSQLLicenseTerms=YES"
    Start-Process msiexec -ArgumentList $ComponentInstallArguments -Wait -NoNewWindow

    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
    }
    $SQLDriver = "https://download.microsoft.com/download/E/6/B/E6BFDC7A-5BCD-4C51-9912-635646DA801E/msodbcsql_17.1.0.1_x64.msi"
    $SQLDriverInstaller = "msodbcsql_17.1.0.1_x64.msi"
    Write-Host "Downloading SQL ODBC Driver."
    try {
        Start-BitsTransfer $SQLDriver -ErrorAction Stop
        $SQLDriverDownloaded = $true
    }
    catch {
        Write-Host "Unable to download the SQL driver."
    }

    if ($SQLDriverDownloaded -eq $true) {
        Write-Host "Installing SQL ODBC Driver."
        $ComponentInstallArguments = "/i",$SQLDriverInstaller,$Global:GUILevel,"/norestart","IAcceptMSODBCSQLLicenseTerms=YES"
        Start-Process msiexec -ArgumentList $ComponentInstallArguments -Wait -NoNewWindow
    }

    Write-Host "Installing observeit Web Console."
    $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
    Write-Host "Setting local firewall."
    $FWRuleAdd = New-NetFirewallRule -DisplayName "observeit Web Categorization module" -Direction Inbound â€“Protocol TCP â€“LocalPort 8000 -Action allow
    $ComponentInstallArguments = "/i",$Global:WebCatInstaller,"/qf","/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, NET-Framework-45-Core, NET-Framework-45-Features, NET-Framework-45-ASPNET â€“IncludeManagementTools -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" -and $_.DisplayName -ne "ObserveITAgent"}

    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
    
    Find-OITInstalledComponents
    if (!$Global:InstalledProducts) {
        Write-Host "No products are currently installed." -ForegroundColor Yellow
    }

    $Global:InstalledProducts
    Find-OITCurrentDBServer

    Write-Host "Stopping services:" -ForegroundColor Yellow
    Stop-OITServices -IIS -observeit -WebsiteCat
    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 "Currently installed products:"
    Find-OITInstalledComponents
    $Global:InstalledProducts

    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 Find-OITCurrentDBServer {
    Find-OITPaths
    $CurrentResult = @()
    foreach ($item in $Global:OITBackendConfigFullPath) {
        try {
            $CurrentString = $(Get-Content $item -ErrorAction Stop| Select-String -Pattern '<add name="ConnectionString" connectionString="Data Source=') -match 'Data Source=(=?.*?;)'
            $CurrentString = $Matches[0]
            $CurrentString = $CurrentString.Replace('Data Source=','').Replace(';','')
            $CurrentResult += $CurrentString
        }
        catch {
            Write-Host "Could not find file " $item
        }
    }
    if ($CurrentResult) {
        Write-Host "Current database server address is " -NoNewline
        Write-Host "$($CurrentResult | Select-object -unique)" -ForegroundColor Yellow
    } else {
        Write-Host "Name of the current database server not found." -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"
        try {
            Copy-Item -Path $OITConfigFile -Destination $BackupFileName -Force -ErrorAction Stop
        }
        catch {
            Write-Host "File $BackupFileName not found."
        }
    }
}
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-OITVisualDataRetention {
    <#
    .SYNOPSIS
     
    Performs retention of observeit screenshot data. Written by Ze'ev Cohen - March 2018.
    .DESCRIPTION
     
    This utility scans the file system in order to delete screenshots of ObsreveIT leaving the most newer ones.
    .PARAMETER Path
     
    Specify the root path of observeit screenshot data storage.
    .PARAMETER DaysToKeep
     
    Specify the retention time, in days.
    .EXAMPLE
     
    Start-OITVisualDataRetention -Path C:\OITData\FS -DaysToKeep 90
    #>


    Param(
        # Specify path
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        $Path = "n/a",
        # Specify retention time
        [Parameter(Mandatory=$true,Position=1)]
        [int]
        $DaysToKeep = "n/a"
    )

    # Manage command line parameters
    $numOfArgs = $args.Length-1
    for ($i=0;$i -le $numOfArgs; $i++){
        switch($args[$i]){
            '-Path' { 
                $i++ 
                $Path = $args[$i]
                continue
            }
            '-DaysToKeep' {
                $i++
                $DaysToKeep = $args[$i]
                continue
            }
            default {
                $ol = "Invalid Command-line Paramter: " + $args[$i]
                Write-Host $ol -ForegroundColor Yellow
                exit
            }
        }
    }
        
    Write-Output ""
    Write-Output "ObserveIT Del Screenshot v1.1"
    Write-Output "============================="
    Write-Output ""

    # Check for PowerShell verison
    if ($PSVersionTable.WSManStackVersion.Major -lt 3){
        Write-Out "PowerShell v3 or above should be installed on this server."
        exit;
    }

    
    if ($Path -eq "n/a" -Or $DaysToKeep -eq "n/a") {
        Write-Output "Usage: OITDelScreenShot <parameters as shown below>"
        Write-Output " "
        Write-Output ' <-Path "Path of ObserveIT images to scan and delete"> <-DaysToKeep # of days to keep the images, delete all older>'
        Write-Output ""
        Write-Output " Examples:"
        Write-Output ""
        Get-Help Start-OITVisualDataRetention -Examples
        exit
    }

    $path = $path.TrimEnd("\")+"\"
    if (Test-Path $path){
        # Calculate the oldest date, delete all older
        $dt = (Get-Date).AddDays(-$DaysToKeep)

        Write-Host "Deleting Screen-Shots folders older than" $dt.ToString('yyyy\\M\\d') -ForegroundColor Cyan
        Write-Host ""

        $dt = [int](Get-Date $dt -Format ('yyyyMMdd'))

        $delCnt = 0

        # Read the file-system folder structure, Years, Month, Day
        ###$years = (Get-ChildItem -Path $Path.ToString()).FullName | Sort #-Descending
        $years = dir -Directory $Path | Sort-Object -Property {$_.Name -as [int]}
    
        # Loop on the subfolder containing the Years
        for ($iy = 0; $iy -lt $years.Count ; $iy++) {
            $yearfolder = $years[$iy].FullName
            $yearmonths = dir -Directory $yearfolder | Sort-Object -Property {$_.Name -as [int]}

            # Loop each Year's folder for the Month it contains
            for ($im = 0; $im -lt $yearmonths.Count ; $im++) {
                $yearmonthfolder = $yearmonths[$im].FullName

                $yearmonth = $yearmonthfolder.Substring($path.Length)
                if ($yearmonth.Length -gt 5){
                        $y = [int]$yearmonth.Substring(0,4)
                        $m = [int]$yearmonth.Substring(5)
                        if (($m -gt 0) -and ($m -lt 13) -and ($m.Length -lt 3)){
                            $pathDateObj = [int](Get-Date -Year $y -Month $m -Day 1 -Format('yyyyMMdd')).ToString()
                            Write-Host "`rChecking " $y $m " " -NoNewLine

                            # Check if the Year-Month is in the Deletion Range required
                            if ($pathDateObj -le $dt){
                                Write-Host "`rWill delete " $y $m " " -NoNewLine

                                # Get a list of the Days for a specific Year-Month
                                $days = dir -Directory $yearmonthfolder | Sort-Object -Property {$_.Name -as [int]}
                                for ($id = 0; $id -lt $days.Count ; $id++) {
                                    $dd = $days[$id].FullName
                                    $d = [int]$dd.Substring($yearmonthfolder.Length+1)
                                    $delDay = [int](Get-Date -Year $y -Month $m -Day $d -Format('yyyyMMdd')).ToString()
                                    Write-Host "`rChecking " $delDay " " -NoNewLine

                                    # Check if the Year-Month-Day is in older than Deletion Range required
                                    if ($delDay -lt $dt){
                                        Write-Host "`rDeleting: " $y $m $d $dayDel " " -NoNewLine
                                        $cmdExe = Start-Process -FilePath cmd.exe -ArgumentList "/c rd /S /Q $dd" -Wait -WindowStyle Hidden -PassThru
                                        $cmdErr = $cmdExe.ExitCode
                                        $delCnt ++
                                    }
                                }
                                $emptydirectory = Get-Item -Path $yearmonthfolder
                                if (!($emptydirectory.EnumerateFileSystemInfos() | Select-Object -First 1))
                                {
                                    $cmdExe = Start-Process -FilePath cmd.exe -ArgumentList "/c rd /S /Q $yearmonthfolder" -Wait -WindowStyle Hidden -PassThru
                                    $cmdErr = $cmdExe.ExitCode
                                }
                            } 
                        }
                }      
            }
            $emptydirectory = Get-Item -Path $yearfolder
            if (!($emptydirectory.EnumerateFileSystemInfos() | Select-Object -First 1))
            {
                $cmdExe = Start-Process -FilePath cmd.exe -ArgumentList "/c rd /S /Q $yearmonthfolder" -Wait -WindowStyle Hidden -PassThru
                $cmdErr = $cmdExe.ExitCode
            }                                      
        }
        do {
            $dirs = Get-ChildItem $path -directory -recurse | Where-Object { (Get-ChildItem $_.fullName -Force).count -eq 0 } | Select-Object -expandproperty FullName
            $dirs | Foreach-Object { Remove-Item $_ }
        } while ($dirs.count -gt 0)           

        Write-Host ""
        Write-Host ""
        Write-Host "Deleted: " $delCnt " Day(s)"
    } else {
        Write-Host "Screen-Shots folder does not exist" -ForegroundColor Red
        exit
    }
}
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
        }

function Get-OITInfoCollection {
    <#
    .Synopsis
        Collects ObserveIT configuration files and trace data.
    .DESCRIPTION
        Searches the specified ObserveIT folder for configuration and trace files, and zips them into a single file for easy transfer.
    .PARAMETER OITInstallationPath
        Root path where ObserveIT is installed. Default is C:\Program Files\ObserveIT.
    .PARAMETER DestinationPath
        Path to where the resulting file should be saved. Default is current profile's TEMP folder.
    .EXAMPLE
        Get-OITInfoCollection -OITInstallationPath 'C:\Program Files\ObserveIT' -DestinationPath C:\Temp
    #>

    param (
        # Path to the ObserveIT installation folder
        [Parameter(Mandatory=$false)]
        [string]
        $OITInstallationPath = 'C:\Program Files\ObserveIT',
        # Destination for the zip file output
        [Parameter(Mandatory=$false)]
        [string]
        $DestinationPath = $env:TEMP
    )

    $FilesToSearchFor = @(
        "*.config",
        "*.txt",
        "*.log"
    )

    $CollectorFileName = $env:COMPUTERNAME + "_" + "OITLogCollector$(Get-Date -Format yyyymmddhhmmss)"
    $TempFolder = $env:TEMP + '\' + $CollectorFileName
    $DestinationPath = $DestinationPath + '\' + $CollectorFileName

    Write-Host "Testing availability of the destination path: $OITInstallationPath"
    $InstallationExists = Test-Path $OITInstallationPath
    
    if ($InstallationExists -eq $false) {
        Write-Host "FATAL ERROR: ObserveIT installation path does not exists or no perimssions to access."
        break
    }

    Write-Host 'Creating a temporary folder.'
    try {
        New-Item $TempFolder -ItemType Directory -Force -ErrorAction Stop | Out-Null
        Write-Host 'Done.'
    }
    catch {
        Write-Host 'FATAL ERROR: Unable to create a folder at the following path:' $TempFolder
        Write-Host 'Quitting.'
        break
    }

    Write-Host "Collecting files."
    foreach ($item in $FilesToSearchFor) {
        $FilesToCopy = Get-ChildItem $OITInstallationPath\* -Recurse -Include $item -ErrorAction Ignore -Exclude 'LICENSE.txt'
        foreach ($file in $FilesToCopy) {
            $ParentFolderName = $file.fullName -split ('\\')
            $ArrayLength = $ParentFolderName.Length - 2
            $ParentFolderName = $ParentFolderName[$ArrayLength]
            $ParentFolderPath = $TempFolder + '\' + $ParentFolderName
            if (!$(Test-Path $ParentFolderPath)) {
                New-Item $ParentFolderPath -ItemType Directory -Force | Out-Null
            }
            try {
                Copy-Item $file -Destination $ParentFolderPath -ErrorAction Stop
            }
            catch {
                Write-Host "Error copying item" $file -ForegroundColor Red
            }
        }
    }

    Write-Host 'Export Windows Event Log data'
    Get-EventLog Application -After $((Get-Date).AddDays(-3)) | fl > $TempFolder\EventLogApplication.log
    Get-EventLog System -After $((Get-Date).AddDays(-3)) | fl > $TempFolder\EventLogSystem.log

    Write-Host "Compressing files to the following path: $DestinationPath.zip"
    Compress-Archive -Path $TempFolder -DestinationPath $DestinationPath -CompressionLevel Optimal -Force

    Remove-Item $TempFolder -Recurse -Force
}
function Start-OITADGroupUpdate {
    <#
    .SYNOPSIS
        Updates a security group with members of an Organizational Unit.
    .DESCRIPTION
        This function retrieves users from a specified OU and and adds them to a specified Security Group.
    .PARAMETER SourceOUDN
        DistinguishedName of the OU where the users are located.
    .PARAMETER DestinationSG
        Name of the destination Security Group the users will be added to.
    .PARAMETER DestinationOUDN
        Distinguished Name of the OU the Security Group will be created in.
    .PARAMETER SGScope
        Scope of the security group. Possible values are 'DomainLocal', 'Global', or 'Universal'
    .EXAMPLE
        Start-OITADGroupUpdate -SourceOUDN 'OU=Marketing,OU=LAB,DC=domain,DC=lab' -DestinationSG Test001 -DestinationOUDN 'OU=Groups,OU=LAB,DC=domain,DC=lab' -SGScope Global
    #>

    param (
        [Parameter(Mandatory=$true,Position=0)]
        [string]
        $SourceOUDN,
        [Parameter(Mandatory=$true,Position=1)]
        [string]
        $DestinationSG,
        [Parameter(Mandatory=$true,Position=2)]
        [string]
        $DestinationOUDN,
        [Parameter(Mandatory=$false,Position=3)]
        [ValidateSet("DomainLocal","Global","Universal")] 
        [string]
        $SGScope = 'Global'
    )

    Import-Module ActiveDirectory
    if (!$(Get-ADGroup $DestinationSG -ErrorAction Stop)) {
        Write-Host "Group $DestinationSG not found. Creating new security group under $DestinationOUDN"
        New-ADGroup -Name $DestinationSG -Path $DestinationOUDN -GroupScope $SGScope -Verbose
    }
    $Users = Get-ADUser -Filter * -SearchBase $SourceOUDN
    Write-Host "Adding users to $DestinationSG"
    Get-ADGroup $DestinationSG | Add-ADGroupMember -Members $Users -Verbose
}