# -----------------------------------------------------------------------------
#                     S C R I P T   S P E C I F I C A T I O N
#                     Copyright 2020 Motorola Solutions, Inc.
#                              All Rights Reserved.
#                    Motorola Solutions Confidential Restricted
# -----------------------------------------------------------------------------
#
# NAME
#       TerminateDependentApps.vbs
#
# DESCRIPTION
#       Terminate implementation for Motorola OpenJDK
#
# MODIFICATION HISTORY
# Date       Change
# -----------------------------------------------------------------------------
# 09 Apr 18  Initial creation
# 08 Jan 20  Added windows critical processes exclusion
# 27 Jan 20  Added additional logging
# -----------------------------------------------------------------------------
#

Param (
    [Parameter(Mandatory = $true)][string]$pathToDependentFiles,
    [Parameter(Mandatory = $true)][string]$logFile,
    [Parameter(Mandatory = $true)][bool]$terminate
)

# script exit codes
Set-Variable SUCCESS -option ReadOnly -value 0
Set-Variable FAILURE -option ReadOnly -value 1

# Log messages types
$INFO_C = "Information"
$ERROR_C = "ERROR"
$WARN_C = "Warning"

$criticalProcesses = @("SVCHOST", "SMSS", "CSRSS", "WININIT", "LOGONUI", "LSASS", "SERVICES", "WINLOGON")

Function Log-ToScreenAndFile ([string]$message, $type = $INFO_C) {
    $myDate = Get-Date -format "ddd yyyy-MM-dd h:mm:ss tt"
    $fileMessage = $type + ":" + $myDate + ":TerminateDependentApps.ps1:" + $message
    Write-Host $message
    Log-ToFile $fileMessage
}

Function Log-ToFile ([string]$message) {
    $message += "`r`n"
    try {
        [IO.File]::AppendAllText($logFile, $message)
    }
    catch {
        if ($_ -match "because it is being used by another process") {
            $mypid = [System.Diagnostics.Process]::GetCurrentProcess().ID
            [IO.File]::AppendAllText("$($logFile).$($mypid)", $message)
        }
    }
}

function Get-IsFileLocked($file) {
    $islocked = $true
    try {
        $readonlyFlag = (Get-ItemProperty $file).isReadOnly
        Set-ItemProperty $file -name isReadOnly -value $false
        [IO.File]::OpenWrite("$file").close();
        $islocked = $false
    }
    catch {
        $isLocked = $true
    }
    finally {
        Set-ItemProperty $file -name isReadOnly -value $readonlyFlag
    }
    return $isLocked
}

function Check-LockedFiles ($path) {
    $isFileLocked = $false
    if (Test-Path "$path") {
        $files = (Get-ChildItem "$path" -Recurse | Where-Object { $_.PSIsContainer -eq $false })
        foreach ($file in $files) {
            if (Get-IsFileLocked("$($file.fullname)")) {
                Log-ToScreenAndFile  "Locked Files: $file" $INFO_C
                return $true
            }
        }
    }
    return $isFileLocked
}

function Get-ServicesPIDs {
    return Get-WmiObject -Class Win32_Service | Select-Object -ExpandProperty ProcessId | Select-Object -unique
}

function Get-ServiceNameFromPID ($PIDNumber) {
    return , (Get-WmiObject -Class Win32_Service -Filter "ProcessID = $PIDNumber").Name
}

function Get-SystemPIDs {
    $result = (Get-Process | Where-Object { $_.name -like "*services*" -or $_.name -like "*wininit*" }).id | Select-Object -unique
    return $result
}

function Get-IsMicrosoftService ($serviceName) {
    $service = Get-WmiObject Win32_Service | Where-Object { $_.Name -eq "$serviceName" }
    $path = $($service.PathName.Split("-")[0].Split("/")[0]).replace('"', '')
    return ([System.Diagnostics.FileVersionInfo]::GetVersionInfo($path).LegalCopyright -like "*Microsoft*")
}

function Get-IsSystemProcess {
    Param (
        [Parameter(Mandatory = $true)]$PIDNumber,
        [Parameter(Mandatory = $true)]$SystemPIDs
    )
    $result = $SystemPIDs -contains $PIDNumber
    return $result
}

function Get-IsServiceProcess {
    Param (
        [Parameter(Mandatory = $true)]$PIDNumber,
        [Parameter(Mandatory = $true)]$ServicesPIDs
    )
    $result = $ServicesPIDs -contains $PIDNumber
    return $result
}
function Get-ParentProcess ($processID) {
    $process = $null
    $parentProcess = @(Get-WmiObject -Class Win32_Process -filter "ProcessId = $processID").ParentProcessId
    if ($parentProcess) {
        #checking if parent process is still alive
        $process = @(Get-WmiObject -Class Win32_Process -filter "ProcessId = $parentProcess").ProcessId
    }
    return $process
}

function Get-ChildProcesses ($processID) {
    $processes = @(Get-WmiObject -Class Win32_Process -filter "ParentProcessId = $processID")
    return , $processes
}

function Kill-ProcessTree {
    Param (
        [Parameter(Mandatory = $true)]$processID,
        [Parameter][switch]$withChildren
    )
    if (Get-IsSystemProcess -PIDNumber $processID -SystemPIDs $(get-SystemPIDs)) {
        return
    }
    #parent process handling
    if ($parent = Get-ParentProcess($processID)) {
        $processName = ((Get-Process -id $processID -ErrorAction "SilentlyContinue").name).ToUpper()
        if ($criticalProcesses -Contains $processName) {
            Log-ToScreenAndFile  "Skipping process $processName, PID: $processID, because it is critical for windows system." $INFO_C
            return
        }
        else {
            Kill-ProcessTree ($parent)
        }
    }
    $childProcesses = Get-ChildProcesses ($processID)
    $process = Get-Process -id $processID -ErrorAction "SilentlyContinue" 
    Log-ToScreenAndFile  "Killing process $($process.name), PID: $processID" $INFO_C
    $process | Stop-Process -force -confirm:$false
    $i = 0
    do {
        $process = (Get-Process -id $processID -ErrorAction "SilentlyContinue")
        if ($process) {
            #process still exists. Waiting max 5 seconds
            if ($i -gt 50) {
                $msg = "Timeout expired while killing process $($process.Name) PID: $($process.processID)"
                Log-ToScreenAndFile $msg $ERROR_C
                throw $msg
            }
            Start-Sleep -Milliseconds 100
            $i++
        }
    } while ($process)
    if ($withChildren) {
        if ($childProcesses.count -gt 0) {
            Foreach ($childProcess in $childProcesses.processID) {
                Kill-ProcessTree -processId ($childProcess) -withChildren
            }
        }
    }
}

function Get-DependentProcessesPIDs ($dependentPath) {
    #searching for opened files match pattern
    Write-Host "Path = $dependentPath"
    $antecedents = @()
    Get-Process | ForEach-Object {
        if (Get-Process -name $_.Name -Module -ErrorAction SilentlyContinue | Where-Object { $_.filename -like "*$dependentPath*" }) {
            $antecedents += $_.Id
        }
    }
    if ($Antecedents) {
        Log-ToScreenAndFile "Found PIDs: $Antecedents" $WARN_C
        return $antecedents
    }
    Write-Host "No PIDS $PIDs"
    return @()
}

function Terminate-PackageDependentProcesses ($path) {
    $result = @(Get-DependentProcessesPIDs -dependentPath ("$path"))
    $ServicesPIDs = Get-ServicesPIDs
    foreach ($item in $result) {
        do {
            #checking if process is part of service
            if (Get-IsServiceProcess -PIDNumber $item -servicesPIDs $ServicesPIDs) {
                $servicesName = Get-ServiceNameFromPID -PIDNumber $item
                if ($servicesName) {
                    foreach ($serviceName in $servicesName) {
                        if (-not (Get-IsMicrosoftService($serviceName))) {
                            Log-ToScreenAndFile  "Stopping service $serviceName" $INFO_C
                            Get-Service $serviceName | Stop-Service
                        }
                        else {
                            Log-ToScreenAndFile  "Service $serviceName is system service. Skipping" $INFO_C
                        }
                    }
                }
            }
            $item = (Get-WmiObject -Class Win32_Process -Filter "ProcessId = $item").ParentProcessId
        } while ($item)
    }

    #all services should be stopped. Checking if there is any processes
    $result = @(get-dependentProcessesPIDs -dependentPath ("$path"))
    if ($result) {
        Log-ToScreenAndFile "There are sill running processes. Trying to terminate" $INFO_C
        foreach ($item in $result) {
            if ((Kill-ProcessTree -processID $item) -eq $false) {
                return $false
            }
        }
    }
    return $true
}

if (-not (Test-Path $pathToDependentFiles)) {
    Log-ToScreenAndFile "Path $pathToDependentFiles not exists. There is nothing to terminate." $INFO_C
}
else {
    if ($terminate) {
        Log-ToScreenAndFile "Checking if there are locked files" $INFO_C
        #obtaining real path (if given path is shortcut)
        if ((Get-Item $pathToDependentFiles).target) {
            $pathToDependentFiles = (Get-Item $pathToDependentFiles).target
        }
        if (check-lockedFiles -path "$pathToDependentFiles") {
            Log-ToScreenAndFile "Terminating services and processes" $INFO_C
            $result = terminate-PackageDependentProcesses -path "$pathToDependentFiles"
            if ($result -eq $false) {
                Log-ToScreenAndFile "Cannot stop all dependent process or services" $ERROR_C
                exit $FAILURE
            }
        }
    }
    else {
        Log-ToScreenAndFile "Making sure all locks are released" $INFO_C
        if ($LockedFiles = check-lockedFiles -path "$pathToDependentFiles") {
            Log-ToScreenAndFile "There are still locked files. Exitting with error" $WARN_C

            $LockedFiles

            $DependentProcessesPIDs = Get-DependentProcessesPIDs $pathToDependentFiles

            ForEach ($ProcessID in $DependentProcessesPIDs) {
                $ServiceName = Get-ServiceNameFromPID $ProcessID
                if ($ServiceName) {
                    Log-ToScreenAndFile "PID: $ProcessID handled by service: $ServiceName" $WARN_C
                }
                else {
                    try {
                        $ProcessName = (Get-Process -PID $ProcessID -ErrorAction Stop).Name
                    }
                    catch {
                        $ProcessName = "Process Name Not Found"
                    }
                    Log-ToScreenAndFile "PID: $ProcessID handled by process: $ProcessName" $WARN_C
                }
            }
        }
    }
}

exit $SUCCESS
