text:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

#include "serverprocess.h"

#include <IPHlpApi.h>
#include "EventLog.h"
#include "file_utility.h"
#include "exceptions.h"
//#include <share.h>

//extern BOOL g_fNsiApiNotSupported;

#define STARTUP_TIME_LIMIT_INCREMENT_IN_MILLISECONDS 5000

HRESULT
SERVER_PROCESS::Initialize(
    PROCESS_MANAGER      *pProcessManager,
    STRU                 *pszProcessExePath,
    STRU                 *pszArguments,
    DWORD                 dwStartupTimeLimitInMS,
    DWORD                 dwShtudownTimeLimitInMS,
    BOOL                  fWindowsAuthEnabled,
    BOOL                  fBasicAuthEnabled,
    BOOL                  fAnonymousAuthEnabled,
    std::map<std::wstring, std::wstring, ignore_case_comparer>& pEnvironmentVariables,
    BOOL                  fStdoutLogEnabled,
    BOOL                  fEnableOutOfProcessConsoleRedirection,
    BOOL                  fWebSocketSupported,
    STRU                  *pstruStdoutLogFile,
    STRU                  *pszAppPhysicalPath,
    STRU                  *pszAppPath,
    STRU                  *pszAppVirtualPath,
    STRU                  *pszHttpsPort
)
{
    HRESULT hr = S_OK;

    m_pProcessManager = pProcessManager;
    m_dwStartupTimeLimitInMS = dwStartupTimeLimitInMS;
    m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS;
    m_fStdoutLogEnabled = fStdoutLogEnabled;
    m_fWebSocketSupported = fWebSocketSupported;
    m_fWindowsAuthEnabled = fWindowsAuthEnabled;
    m_fBasicAuthEnabled = fBasicAuthEnabled;
    m_fAnonymousAuthEnabled = fAnonymousAuthEnabled;
    m_fEnableOutOfProcessConsoleRedirection = fEnableOutOfProcessConsoleRedirection;
    m_pProcessManager->ReferenceProcessManager();
    m_fDebuggerAttached = FALSE;

    if (FAILED_LOG(hr = m_ProcessPath.Copy(*pszProcessExePath)) ||
        FAILED_LOG(hr = m_struLogFile.Copy(*pstruStdoutLogFile))||
        FAILED_LOG(hr = m_struPhysicalPath.Copy(*pszAppPhysicalPath))||
        FAILED_LOG(hr = m_struAppFullPath.Copy(*pszAppPath))||
        FAILED_LOG(hr = m_struAppVirtualPath.Copy(*pszAppVirtualPath))||
        FAILED_LOG(hr = m_Arguments.Copy(*pszArguments)) ||
        FAILED_LOG(hr = m_struHttpsPort.Copy(*pszHttpsPort)) ||
        FAILED_LOG(hr = SetupJobObject()))
    {
        goto Finished;
    }

    m_pEnvironmentVarTable = pEnvironmentVariables;

Finished:
    return hr;
}

HRESULT
SERVER_PROCESS::SetupJobObject(VOID)
{
    HRESULT                                 hr = S_OK;
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION    jobInfo = { 0 };

    if (m_hJobObject == NULL)
    {
        m_hJobObject = CreateJobObject(NULL,   // LPSECURITY_ATTRIBUTES
            NULL); // LPCTSTR lpName
#pragma warning( disable : 4312)
        // 0xdeadbeef is used by Antares
        if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef)
        {
            m_hJobObject = NULL;
            // ignore job object creation error.
        }
#pragma warning( error : 4312)
        if (m_hJobObject != NULL)
        {
            jobInfo.BasicLimitInformation.LimitFlags =
                JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;

            if (!SetInformationJobObject(m_hJobObject,
                JobObjectExtendedLimitInformation,
                &jobInfo,
                sizeof jobInfo))
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }

    return hr;
}

HRESULT
SERVER_PROCESS::GetRandomPort
(
    DWORD* pdwPickedPort,
    DWORD  dwExcludedPort = 0
)
{
    HRESULT hr = S_OK;
    BOOL    fPortInUse = FALSE;
    DWORD   dwActualProcessId = 0;

    std::uniform_int_distribution<> dist(MIN_PORT, MAX_PORT);

    if (g_fNsiApiNotSupported)
    {
        //
        // the default value for optional parameter dwExcludedPort is 0 which is reserved
        // a random number between MIN_PORT and MAX_PORT
        //
        while ((*pdwPickedPort = dist(m_randomGenerator)) == dwExcludedPort);
    }
    else
    {
        DWORD cRetry = 0;
        do
        {
            //
            // ignore dwActualProcessId because here we are
            // determing whether the randomly generated port is
            // in use by any other process.
            //
            while ((*pdwPickedPort = dist(m_randomGenerator)) == dwExcludedPort);
            hr = CheckIfServerIsUp(*pdwPickedPort, &dwActualProcessId, &fPortInUse);
        } while (fPortInUse && ++cRetry < MAX_RETRY);

        if (cRetry >= MAX_RETRY)
        {
            hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET);
        }
    }

    return hr;
}

HRESULT
SERVER_PROCESS::SetupListenPort(
    ENVIRONMENT_VAR_HASH    *pEnvironmentVarTable,
    BOOL*                    pfCriticalError
)
{
    HRESULT hr = S_OK;
    ENVIRONMENT_VAR_ENTRY *pEntry = NULL;
    *pfCriticalError = FALSE;

    pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry);
    if (pEntry != NULL)
    {
        if (pEntry->QueryValue() != NULL && pEntry->QueryValue()[0] != L'\0')
        {
            m_dwPort = (DWORD)_wtoi(pEntry->QueryValue());
            if (m_dwPort >MAX_PORT || m_dwPort < MIN_PORT)
            {
                hr = E_INVALIDARG;
                *pfCriticalError = TRUE;
                goto Finished;
                // need add log for this one
            }
            hr = m_struPort.Copy(pEntry->QueryValue());
            goto Finished;
        }
        else
        {
            //
            // user set the env variable but did not give value, let's set it up
            //
            pEnvironmentVarTable->DeleteKey(ASPNETCORE_PORT_ENV_STR);
            pEntry->Dereference();
            pEntry = NULL;
        }
    }

    WCHAR buffer[15];
    if (FAILED_LOG(hr = GetRandomPort(&m_dwPort)))
    {
        goto Finished;
    }

    if (swprintf_s(buffer, 15, L"%d", m_dwPort) <= 0)
    {
        hr = E_INVALIDARG;
        goto Finished;
    }

    pEntry = new ENVIRONMENT_VAR_ENTRY();
    if (pEntry == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto Finished;
    }

    if (FAILED_LOG(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) ||
        FAILED_LOG(hr = pEnvironmentVarTable->InsertRecord(pEntry)) ||
        FAILED_LOG(hr = m_struPort.Copy(buffer)))
    {
        goto Finished;
    }

Finished:
    if (pEntry != NULL)
    {
        pEntry->Dereference();
        pEntry = NULL;
    }

    if (FAILED_LOG(hr))
    {
        EventLog::Error(
            ASPNETCORE_EVENT_PROCESS_START_SUCCESS,
            ASPNETCORE_EVENT_PROCESS_START_PORTSETUP_ERROR_MSG,
            m_struAppFullPath.QueryStr(),
            m_struPhysicalPath.QueryStr(),
            m_dwPort,
            MIN_PORT,
            MAX_PORT,
            hr);
    }

    return hr;
}

HRESULT
SERVER_PROCESS::SetupAppPath(
    ENVIRONMENT_VAR_HASH*    pEnvironmentVarTable
)
{
    HRESULT      hr = S_OK;
    ENVIRONMENT_VAR_ENTRY*  pEntry = NULL;

    pEnvironmentVarTable->FindKey(ASPNETCORE_APP_PATH_ENV_STR, &pEntry);
    if (pEntry != NULL)
    {
        // user should not set this environment variable in configuration
        pEnvironmentVarTable->DeleteKey(ASPNETCORE_APP_PATH_ENV_STR);
        pEntry->Dereference();
        pEntry = NULL;
    }

    pEntry = new ENVIRONMENT_VAR_ENTRY();
    if (pEntry == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto Finished;
    }

    if (FAILED_LOG(hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppVirtualPath.QueryStr())) ||
        FAILED_LOG(hr = pEnvironmentVarTable->InsertRecord(pEntry)))
    {
        goto Finished;
    }

Finished:
    if (pEntry != NULL)
    {
        pEntry->Dereference();
        pEntry = NULL;
    }
    return hr;
}

HRESULT
SERVER_PROCESS::SetupAppToken(
    ENVIRONMENT_VAR_HASH    *pEnvironmentVarTable
)
{
    HRESULT     hr = S_OK;
    UUID        logUuid;
    PSTR        pszLogUuid = NULL;
    BOOL        fRpcStringAllocd = FALSE;
    RPC_STATUS  rpcStatus;
    STRU        strAppToken;
    ENVIRONMENT_VAR_ENTRY*  pEntry = NULL;

    pEnvironmentVarTable->FindKey(ASPNETCORE_APP_TOKEN_ENV_STR, &pEntry);
    if (pEntry != NULL)
    {
        // user sets the environment variable
        m_straGuid.Reset();
        hr = m_straGuid.CopyW(pEntry->QueryValue());
        pEntry->Dereference();
        pEntry = NULL;
        goto Finished;
    }
    else
    {
        if (m_straGuid.IsEmpty())
        {
            // the GUID has not been set yet
            rpcStatus = UuidCreate(&logUuid);
            if (rpcStatus != RPC_S_OK)
            {
                hr = rpcStatus;
                goto Finished;
            }

            rpcStatus = UuidToStringA(&logUuid, (BYTE **)&pszLogUuid);
            if (rpcStatus != RPC_S_OK)
            {
                hr = rpcStatus;
                goto Finished;
            }

            fRpcStringAllocd = TRUE;

            if (FAILED_LOG(hr = m_straGuid.Copy(pszLogUuid)))
            {
                goto Finished;
            }
        }

        pEntry = new ENVIRONMENT_VAR_ENTRY();
        if (pEntry == NULL)
        {
            hr = E_OUTOFMEMORY;
            goto Finished;
        }

        if (FAILED_LOG(strAppToken.CopyA(m_straGuid.QueryStr())) ||
            FAILED_LOG(hr = pEntry->Initialize(ASPNETCORE_APP_TOKEN_ENV_STR, strAppToken.QueryStr())) ||
            FAILED_LOG(hr = pEnvironmentVarTable->InsertRecord(pEntry)))
        {
            goto Finished;
        }
    }

Finished:

    if (fRpcStringAllocd)
    {
        RpcStringFreeA((BYTE **)&pszLogUuid);
        pszLogUuid = NULL;
    }
    if (pEntry != NULL)
    {
        pEntry->Dereference();
        pEntry = NULL;
    }
    return hr;
}

HRESULT
SERVER_PROCESS::OutputEnvironmentVariables
(
    MULTISZ*                pmszOutput,
    ENVIRONMENT_VAR_HASH*   pEnvironmentVarTable
)
{
    HRESULT    hr = S_OK;
    LPWSTR     pszEnvironmentVariables = NULL;
    LPWSTR     pszCurrentVariable = NULL;
    LPWSTR     pszNextVariable = NULL;
    LPWSTR     pszEqualChar = NULL;
    STRU       strEnvVar;
    ENVIRONMENT_VAR_ENTRY* pEntry = NULL;

    DBG_ASSERT(pmszOutput);
    DBG_ASSERT(pEnvironmentVarTable); // We added some startup variables
    DBG_ASSERT(pEnvironmentVarTable->Count() >0);

    // cleanup, as we may in retry logic
    pmszOutput->Reset();

    pszEnvironmentVariables = GetEnvironmentStringsW();
    if (pszEnvironmentVariables == NULL)
    {
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT);
        goto Finished;
    }
    pszCurrentVariable = pszEnvironmentVariables;
    while (*pszCurrentVariable != L'\0')
    {
        pszNextVariable = pszCurrentVariable + wcslen(pszCurrentVariable) + 1;
        pszEqualChar = wcschr(pszCurrentVariable, L'=');
        if (pszEqualChar != NULL)
        {
            if (FAILED_LOG(hr = strEnvVar.Copy(pszCurrentVariable, (DWORD)(pszEqualChar - pszCurrentVariable) + 1)))
            {
                goto Finished;
            }
            pEnvironmentVarTable->FindKey(strEnvVar.QueryStr(), &pEntry);
            if (pEntry != NULL)
            {
                // same env variable is defined in configuration, use it
                if (FAILED_LOG(hr = strEnvVar.Append(pEntry->QueryValue())))
                {
                    goto Finished;
                }
                pmszOutput->Append(strEnvVar);  //should we check the returned bool
                // remove the record from hash table as we already output it
                pEntry->Dereference();
                pEnvironmentVarTable->DeleteKey(pEntry->QueryName());
                strEnvVar.Reset();
                pEntry = NULL;
            }
            else
            {
                pmszOutput->Append(pszCurrentVariable);
            }
        }
        else
        {
            // env varaible is not well formated
            hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT);
            goto Finished;
        }
        // move to next env variable
        pszCurrentVariable = pszNextVariable;
    }
    // append the remaining env variable in hash table
    pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HELPERS::CopyToMultiSz, pmszOutput);

Finished:
    if (pszEnvironmentVariables != NULL)
    {
        FreeEnvironmentStringsW(pszEnvironmentVariables);
        pszEnvironmentVariables = NULL;
    }
    return hr;
}

HRESULT
SERVER_PROCESS::SetupCommandLine(
    STRU*      pstrCommandLine
)
{
    HRESULT    hr = S_OK;
    LPWSTR     pszPath = NULL;
    LPWSTR     pszFullPath = NULL;
    STRU       strRelativePath;
    DWORD      dwBufferSize = 0;
    FILE       *file = NULL;

    DBG_ASSERT(pstrCommandLine);

    if (!m_struCommandLine.IsEmpty() &&
        pstrCommandLine == (&m_struCommandLine))
    {
        // already set up the commandline string, skip
        goto Finished;
    }

    pszPath = m_ProcessPath.QueryStr();

    if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL))
    {
        // let's check whether it is a relative path
        if (FAILED_LOG(hr = strRelativePath.Copy(m_struPhysicalPath.QueryStr())) ||
            FAILED_LOG(hr = strRelativePath.Append(L"\\")) ||
            FAILED_LOG(hr = strRelativePath.Append(pszPath)))
        {
            goto Finished;
        }

        dwBufferSize = strRelativePath.QueryCCH() + 1;
        pszFullPath = new WCHAR[dwBufferSize];
        if (pszFullPath == NULL)
        {
            hr = E_OUTOFMEMORY;
            goto Finished;
        }

        if (_wfullpath(pszFullPath,
            strRelativePath.QueryStr(),
            dwBufferSize) == NULL)
        {
            hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
            goto Finished;
        }

        if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL)
        {
            fclose(file);
            pszPath = pszFullPath;
        }
    }
    if (FAILED_LOG(hr = pstrCommandLine->Copy(L"\"")) ||
        FAILED_LOG(hr = pstrCommandLine->Append(pszPath)) ||
        FAILED_LOG(hr = pstrCommandLine->Append(L"\" ")) ||
        FAILED_LOG(hr = pstrCommandLine->Append(m_Arguments.QueryStr())))
    {
        goto Finished;
    }

Finished:
    if (pszFullPath != NULL)
    {
        delete pszFullPath;
    }
    return hr;
}

HRESULT
SERVER_PROCESS::PostStartCheck(
    VOID
)
{
    HRESULT hr = S_OK;

    BOOL    fReady = FALSE;
    BOOL    fProcessMatch = FALSE;
    BOOL    fDebuggerAttached = FALSE;
    DWORD   dwTickCount = 0;
    DWORD   dwTimeDifference = 0;
    DWORD   dwActualProcessId = 0;
    INT     iChildProcessIndex = -1;
    STACK_STRU(strEventMsg, 256);

    if (CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0)
    {
        // some error occurred  - assume debugger is not attached;
        fDebuggerAttached = FALSE;
    }

    dwTickCount = GetTickCount();

    do
    {
        DWORD processStatus = 0;
        if (GetExitCodeProcess(m_hProcessHandle, &processStatus))
        {
            // make sure the process is still running
            if (processStatus != STILL_ACTIVE)
            {
                // double check
                if (GetExitCodeProcess(m_hProcessHandle, &processStatus) && processStatus != STILL_ACTIVE)
                {
                    hr = E_APPLICATION_ACTIVATION_EXEC_FAILURE;
                    goto Finished;
                }
            }
        }
        //
        // dwActualProcessId will be set only when NsiAPI(GetExtendedTcpTable) is supported
        //
        hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady);
        fDebuggerAttached = IsDebuggerIsAttached();

        if (!fReady)
        {
            Sleep(250);
        }

        dwTimeDifference = (GetTickCount() - dwTickCount);
    } while (fReady == FALSE &&
        ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttached));

    if (!fReady)
    {
        hr = E_APPLICATION_ACTIVATION_TIMED_OUT;
        goto Finished;
    }

    // register call back with the created process
    if (FAILED_LOG(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle)))
    {
        goto Finished;
    }

    //
    // check if debugger is attached after startupTimeout.
    //
    if (!fDebuggerAttached &&
        CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0)
    {
        // some error occurred  - assume debugger is not attached;
        fDebuggerAttached = FALSE;
    }

    if (!g_fNsiApiNotSupported)
    {
        //
        // NsiAPI(GetExtendedTcpTable) is supported. we should check whether processIds matche
        //
        if (dwActualProcessId == m_dwProcessId)
        {
            m_dwListeningProcessId = m_dwProcessId;
            fProcessMatch = TRUE;
        }

        if (!fProcessMatch)
        {
            // could be the scenario that backend creates child process
            if (FAILED_LOG(hr = GetChildProcessHandles()))
            {
                goto Finished;
            }

            for (DWORD i = 0; i < m_cChildProcess; ++i)
            {
                // a child process listen on the assigned port
                if (dwActualProcessId == m_dwChildProcessIds[i])
                {
                    m_dwListeningProcessId = m_dwChildProcessIds[i];
                    fProcessMatch = TRUE;

                    if (m_hChildProcessHandles[i] != NULL)
                    {
                        if (fDebuggerAttached == FALSE &&
                            CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttached) == 0)
                        {
                            // some error occurred  - assume debugger is not attached;
                            fDebuggerAttached = FALSE;
                        }

                        if (FAILED_LOG(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i],
                            m_hChildProcessHandles[i])))
                        {
                            goto Finished;
                        }
                        iChildProcessIndex = i;
                    }
                    break;
                }
            }
        }

        if(!fProcessMatch)
        {
            //
            // process that we created is not listening
            // on the port we specified.
            //
            fReady = FALSE;
            hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED);
            strEventMsg.SafeSnwprintf(
                ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG,
                m_struAppFullPath.QueryStr(),
                m_struPhysicalPath.QueryStr(),
                m_struCommandLine.QueryStr(),
                m_dwPort,
                hr);
            goto Finished;
        }
    }

    if (!fReady)
    {
        //
        // hr is already set by CheckIfServerIsUp
        //
        if (dwTimeDifference >= m_dwStartupTimeLimitInMS)
        {
            hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
            strEventMsg.SafeSnwprintf(
                ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG,
                m_struAppFullPath.QueryStr(),
                m_struPhysicalPath.QueryStr(),
                m_struCommandLine.QueryStr(),
                m_dwPort,
                hr);
        }
        goto Finished;
    }

    if (iChildProcessIndex >= 0)
    {
        //
        // final check to make sure child process listening on HTTP is still UP
        // This is needed because, the child process might have crashed/exited between
        // the previous call to checkIfServerIsUp and RegisterProcessWait
        // and we would not know about it.
        //

        hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady);

        if ((FAILED_LOG(hr) || fReady == FALSE))
        {
            strEventMsg.SafeSnwprintf(
                ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG,
                m_struAppFullPath.QueryStr(),
                m_struPhysicalPath.QueryStr(),
                m_struCommandLine.QueryStr(),
                m_dwPort,
                hr);
            goto Finished;
        }
    }

    //
    // ready to mark the server process ready but before this,
    // create and initialize the FORWARDER_CONNECTION
    //
    if (m_pForwarderConnection == NULL)
    {
        m_pForwarderConnection = new FORWARDER_CONNECTION();
        if (m_pForwarderConnection == NULL)
        {
            hr = E_OUTOFMEMORY;
            goto Finished;
        }

        hr = m_pForwarderConnection->Initialize(m_dwPort);
        if (FAILED_LOG(hr))
        {
            goto Finished;
        }
    }

    if (!g_fNsiApiNotSupported)
    {
        m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE,
                                                FALSE,
                                                m_dwListeningProcessId);
    }

    //
    // mark server process as Ready
    //
    m_fReady = TRUE;

Finished:
    m_fDebuggerAttached = fDebuggerAttached;

    if (FAILED_LOG(hr))
    {
        if (m_pForwarderConnection != NULL)
        {
            m_pForwarderConnection->DereferenceForwarderConnection();
            m_pForwarderConnection = NULL;
        }

        if (!strEventMsg.IsEmpty())
        {
            EventLog::Warn(
                ASPNETCORE_EVENT_PROCESS_START_ERROR,
                strEventMsg.QueryStr());
        }
    }
    return hr;
}

HRESULT
SERVER_PROCESS::StartProcess(
    VOID
)
{
    HRESULT                 hr = S_OK;
    PROCESS_INFORMATION     processInformation = {0};
    STARTUPINFOW            startupInfo = {0};
    DWORD                   dwRetryCount = 2; // should we allow customer to config it
    DWORD                   dwCreationFlags = 0;
    MULTISZ                 mszNewEnvironment;
    ENVIRONMENT_VAR_HASH    *pHashTable = NULL;
    PWSTR                   pStrStage = NULL;
    BOOL                    fCriticalError = FALSE;
    std::map<std::wstring, std::wstring, ignore_case_comparer> variables;

    GetStartupInfoW(&startupInfo);

    //
    // setup stdout and stderr handles to our stdout handle only if
    // the handle is valid.
    //
    SetupStdHandles(&startupInfo);

    while (dwRetryCount > 0)
    {
        m_dwPort = 0;
        dwRetryCount--;
        //
        // generate process command line.
        //
        if (FAILED_LOG(hr = SetupCommandLine(&m_struCommandLine)))
        {
            pStrStage = L"SetupCommandLine";
            goto Failure;
        }

        try
        {
            variables = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable(
                m_pEnvironmentVarTable,
                m_fWindowsAuthEnabled,
                m_fBasicAuthEnabled,
                m_fAnonymousAuthEnabled,
                true, // fAddHostingStartup
                m_struAppFullPath.QueryStr(),
                m_struHttpsPort.QueryStr());

            variables = ENVIRONMENT_VAR_HELPERS::AddWebsocketEnabledToEnvironmentVariables(variables, m_fWebSocketSupported);
        }
        CATCH_RETURN();

        pHashTable = new ENVIRONMENT_VAR_HASH();
        RETURN_IF_FAILED(pHashTable->Initialize(37 /*prime*/));
        // Copy environment variables to old style hash table
        for (auto & variable : variables)
        {
            auto pNewEntry = std::unique_ptr<ENVIRONMENT_VAR_ENTRY, ENVIRONMENT_VAR_ENTRY_DELETER>(new ENVIRONMENT_VAR_ENTRY());
            RETURN_IF_FAILED(pNewEntry->Initialize((variable.first + L"=").c_str(), variable.second.c_str()));
            RETURN_IF_FAILED(pHashTable->InsertRecord(pNewEntry.get()));
        }

        //
        // setup the the port that the backend process will listen on
        //
        if (FAILED_LOG(hr = SetupListenPort(pHashTable, &fCriticalError)))
        {
            pStrStage = L"SetupListenPort";
            goto Failure;
        }

        //
        // get app path
        //
        if (FAILED_LOG(hr = SetupAppPath(pHashTable)))
        {
            pStrStage = L"SetupAppPath";
            goto Failure;
        }

        //
        // generate new guid for each process
        //
        if (FAILED_LOG(hr = SetupAppToken(pHashTable)))
        {
            pStrStage = L"SetupAppToken";
            goto Failure;
        }

        //
        // setup environment variables for new process
        //
        if (FAILED_LOG(hr = OutputEnvironmentVariables(&mszNewEnvironment, pHashTable)))
        {
            pStrStage = L"OutputEnvironmentVariables";
            goto Failure;
        }

        dwCreationFlags = CREATE_NO_WINDOW |
            CREATE_UNICODE_ENVIRONMENT |
            CREATE_SUSPENDED |
            CREATE_NEW_PROCESS_GROUP;

        if (!CreateProcessW(
            NULL,                   // applicationName
            m_struCommandLine.QueryStr(),
            NULL,                   // processAttr
            NULL,                   // threadAttr
            TRUE,                   // inheritHandles
            dwCreationFlags,
            mszNewEnvironment.QueryStr(),
            m_struPhysicalPath.QueryStr(), // currentDir
            &startupInfo,
            &processInformation))
        {
            pStrStage = L"CreateProcessW";
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto Failure;
        }

        m_hProcessHandle = processInformation.hProcess;
        m_dwProcessId = processInformation.dwProcessId;

        if (FAILED_LOG(hr = SetupJobObject()))
        {
            pStrStage = L"SetupJobObject";
            goto Failure;
        }

        if (m_hJobObject != NULL)
        {
            if (!AssignProcessToJobObject(m_hJobObject, m_hProcessHandle))
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                if (hr != HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED))
                {
                    pStrStage = L"AssignProcessToJobObject";
                    goto Failure;
                }
            }
        }

        if (ResumeThread(processInformation.hThread) == -1)
        {
            pStrStage = L"ResumeThread";
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto Failure;
        }

        //
        // need to make sure the server is up and listening on the port specified.
        //
        if (FAILED_LOG(hr = PostStartCheck()))
        {
            pStrStage = L"PostStartCheck";
            goto Failure;
        }

        // Backend process starts successfully. Set retry counter to 0
        dwRetryCount = 0;

        EventLog::Info(
            ASPNETCORE_EVENT_PROCESS_START_SUCCESS,
            ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG,
            m_struAppFullPath.QueryStr(),
            m_dwProcessId,
            m_dwListeningProcessId,
            m_dwPort);

        goto Finished;

    Failure:
        if (fCriticalError)
        {
            // Critical error, no retry need to avoid wasting resource and polluting log
            dwRetryCount = 0;
        }

        EventLog::Warn(
            ASPNETCORE_EVENT_PROCESS_START_ERROR,
            ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG,
            m_struAppFullPath.QueryStr(),
            m_struPhysicalPath.QueryStr(),
            m_struCommandLine.QueryStr(),
            pStrStage,
            hr,
            m_dwPort,
            dwRetryCount);

        if (processInformation.hThread != NULL)
        {
            CloseHandle(processInformation.hThread);
            processInformation.hThread = NULL;
        }

        if (pHashTable != NULL)
        {
            pHashTable->Clear();
            delete pHashTable;
            pHashTable = NULL;
        }

        CleanUp();
    }

Finished:
    if (FAILED_LOG(hr) || m_fReady == FALSE)
    {
        if (m_hStdErrWritePipe != NULL)
        {
            if (m_hStdErrWritePipe != INVALID_HANDLE_VALUE)
            {
                CloseHandle(m_hStdErrWritePipe);
            }

            m_hStdErrWritePipe = NULL;
        }

        if (m_hStdoutHandle != NULL)
        {
            if (m_hStdoutHandle != INVALID_HANDLE_VALUE)
            {
                CloseHandle(m_hStdoutHandle);
            }

            m_hStdoutHandle = NULL;
        }

        if (m_fStdoutLogEnabled)
        {
            m_Timer.CancelTimer();
        }

        EventLog::Error(
            ASPNETCORE_EVENT_PROCESS_START_FAILURE,
            ASPNETCORE_EVENT_PROCESS_START_FAILURE_MSG,
            m_struAppFullPath.QueryStr(),
            m_struPhysicalPath.QueryStr(),
            m_struCommandLine.QueryStr(),
            m_dwPort,
            m_output.str().c_str());
    }
    return hr;
}

HRESULT
SERVER_PROCESS::SetWindowsAuthToken(
    HANDLE hToken,
    LPHANDLE pTargetTokenHandle
)
{
    HRESULT hr = S_OK;

    if (m_hListeningProcessHandle != NULL && m_hListeningProcessHandle != INVALID_HANDLE_VALUE)
    {
        if (!DuplicateHandle( GetCurrentProcess(),
                             hToken,
                             m_hListeningProcessHandle,
                             pTargetTokenHandle,
                             0,
                             FALSE,
                             DUPLICATE_SAME_ACCESS ))
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            goto Finished;
        }
    }

Finished:

    return hr;
}

HRESULT
SERVER_PROCESS::SetupStdHandles(
    LPSTARTUPINFOW pStartupInfo
)
{
    HRESULT                 hr = S_OK;
    SYSTEMTIME              systemTime;
    SECURITY_ATTRIBUTES     saAttr = { 0 };

    STRU                    struPath;

    DBG_ASSERT(pStartupInfo);

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    if (!m_fEnableOutOfProcessConsoleRedirection)
    {
        pStartupInfo->dwFlags = STARTF_USESTDHANDLES;
        pStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
        pStartupInfo->hStdError = INVALID_HANDLE_VALUE;
        pStartupInfo->hStdOutput = INVALID_HANDLE_VALUE;
        return hr;
    }

    if (!m_fStdoutLogEnabled)
    {
        CreatePipe(&m_hStdoutHandle, &m_hStdErrWritePipe, &saAttr, 0 /*nSize*/);

        // Read the stderr handle on a separate thread until we get 30Kb.
        m_hReadThread = CreateThread(
            nullptr,       // default security attributes
            0,          // default stack size
            reinterpret_cast<LPTHREAD_START_ROUTINE>(ReadStdErrHandle),
            this,       // thread function arguments
            0,          // default creation flags
            nullptr);      // receive thread identifier

        pStartupInfo->dwFlags = STARTF_USESTDHANDLES;
        pStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
        pStartupInfo->hStdError = m_hStdErrWritePipe;
        pStartupInfo->hStdOutput = m_hStdErrWritePipe;
        return hr;
    }

    hr = FILE_UTILITY::ConvertPathToFullPath(
                 m_struLogFile.QueryStr(),
                 m_struPhysicalPath.QueryStr(),
                 &struPath);
    if (FAILED_LOG(hr))
    {
        goto Finished;
    }

    GetSystemTime(&systemTime);
    hr = m_struFullLogFile.SafeSnwprintf(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
        struPath.QueryStr(),
        systemTime.wYear,
        systemTime.wMonth,
        systemTime.wDay,
        systemTime.wHour,
        systemTime.wMinute,
        systemTime.wSecond,
        GetCurrentProcessId());
    if (FAILED_LOG(hr))
    {
        goto Finished;
    }

    hr = FILE_UTILITY::EnsureDirectoryPathExist(struPath.QueryStr());
    if (FAILED_LOG(hr))
    {
        goto Finished;
    }

    m_hStdoutHandle = CreateFileW(m_struFullLogFile.QueryStr(),
        FILE_WRITE_DATA,
        FILE_SHARE_READ,
        &saAttr,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    if (m_hStdoutHandle == INVALID_HANDLE_VALUE)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    pStartupInfo->dwFlags = STARTF_USESTDHANDLES;
    pStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
    pStartupInfo->hStdError = m_hStdoutHandle;
    pStartupInfo->hStdOutput = m_hStdoutHandle;
    // start timer to open and close handles regularly.
    m_Timer.InitializeTimer(STTIMER::TimerCallback, &m_struFullLogFile, 3000, 3000);

Finished:
    if (FAILED_LOG(hr))
    {
        pStartupInfo->dwFlags = STARTF_USESTDHANDLES;
        pStartupInfo->hStdInput = INVALID_HANDLE_VALUE;
        pStartupInfo->hStdError = INVALID_HANDLE_VALUE;
        pStartupInfo->hStdOutput = INVALID_HANDLE_VALUE;

        if (m_fStdoutLogEnabled)
        {
            // Log the error
            EventLog::Warn(
                ASPNETCORE_EVENT_CONFIG_ERROR,
                ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG,
                m_struFullLogFile.IsEmpty()? m_struLogFile.QueryStr() : m_struFullLogFile.QueryStr(),
                hr);
        }
        // The log file was not created yet in case of failure. No need to clean it
        m_struFullLogFile.Reset();
    }
    return hr;
}

void
SERVER_PROCESS::ReadStdErrHandle(
    LPVOID pContext
)
{
    auto pLoggingProvider = static_cast<SERVER_PROCESS*>(pContext);
    DBG_ASSERT(pLoggingProvider != NULL);
    pLoggingProvider->ReadStdErrHandleInternal();
}

void
SERVER_PROCESS::ReadStdErrHandleInternal()
{
    const size_t bufferSize = 4096;
    size_t charactersLeft = 30000;
    std::string tempBuffer;

    tempBuffer.resize(bufferSize);

    DWORD dwNumBytesRead = 0;
    while (charactersLeft > 0)
    {
        if (ReadFile(m_hStdoutHandle,
            tempBuffer.data(),
            bufferSize,
            &dwNumBytesRead,
            nullptr))
        {
            auto text = to_wide_string(tempBuffer, dwNumBytesRead, GetConsoleOutputCP());
            auto const writeSize = min(charactersLeft, text.size());
            m_output.write(text.c_str(), writeSize);
            charactersLeft -= writeSize;
        }
        else
        {
            return;
        }
    }

    // Continue reading from console out until the program ends or the handle is invalid.
    // Otherwise, the program may hang as nothing is reading stdout.
    while (ReadFile(m_hStdoutHandle,
        tempBuffer.data(),
        bufferSize,
        &dwNumBytesRead,
        nullptr))
    {
    }
}

HRESULT
SERVER_PROCESS::CheckIfServerIsUp(
    _In_  DWORD       dwPort,
    _Out_ DWORD     * pdwProcessId,
    _Out_ BOOL      * pfReady
)
{
    HRESULT                 hr = S_OK;
    DWORD                   dwResult = ERROR_INSUFFICIENT_BUFFER;
    MIB_TCPTABLE_OWNER_PID *pTCPInfo = NULL;
    MIB_TCPROW_OWNER_PID   *pOwner = NULL;
    DWORD                   dwSize = 1000; // Initial size for pTCPInfo buffer
    int                     iResult = 0;
    SOCKADDR_IN             sockAddr;
    SOCKET                  socketCheck = INVALID_SOCKET;

    DBG_ASSERT(pfReady);
    DBG_ASSERT(pdwProcessId);

    *pfReady = FALSE;
    //
    // it's OK for us to return processID 0 in case we cannot detect the real one
    //
    *pdwProcessId = 0;

    if (!g_fNsiApiNotSupported)
    {
        while (dwResult == ERROR_INSUFFICIENT_BUFFER)
        {
            // Increase the buffer size with additional space, MIB_TCPROW 20 bytes
            // New entries may be added by other processes before calling GetExtendedTcpTable
            dwSize += 200;

            if (pTCPInfo != NULL)
            {
                HeapFree(GetProcessHeap(), 0, pTCPInfo);
            }

            pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize);
            if (pTCPInfo == NULL)
            {
                hr = E_OUTOFMEMORY;
                goto Finished;
            }

            dwResult = GetExtendedTcpTable(pTCPInfo,
                &dwSize,
                FALSE,
                AF_INET,
                TCP_TABLE_OWNER_PID_LISTENER,
                0);

            if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER)
            {
                hr = HRESULT_FROM_WIN32(dwResult);
                goto Finished;
            }
        }

        // iterate pTcpInfo struct to find PID/PORT entry
        for (DWORD dwLoop = 0; dwLoop < pTCPInfo->dwNumEntries; dwLoop++)
        {
            pOwner = &pTCPInfo->table[dwLoop];
            if (ntohs((USHORT)pOwner->dwLocalPort) == dwPort)
            {
                *pdwProcessId = pOwner->dwOwningPid;
                *pfReady = TRUE;
                break;
            }
        }
    }
    else
    {
        //
        // We have to open socket to ping the service
        //
        socketCheck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

        if (socketCheck == INVALID_SOCKET)
        {
            hr = HRESULT_FROM_WIN32(WSAGetLastError());
            goto Finished;
        }

        sockAddr.sin_family = AF_INET;
        if (!inet_pton(AF_INET, LOCALHOST, &(sockAddr.sin_addr)))
        {
            hr = HRESULT_FROM_WIN32(WSAGetLastError());
            goto Finished;
        }

        //sockAddr.sin_addr.s_addr = inet_addr( LOCALHOST );
        sockAddr.sin_port = htons((u_short)dwPort);

        //
        // Connect to server.
        // if connection fails, socket is not closed, we reuse the same socket
        // while retrying
        //
        iResult = connect(socketCheck, (SOCKADDR *)&sockAddr, sizeof(sockAddr));
        if (iResult == SOCKET_ERROR)
        {
            hr = HRESULT_FROM_WIN32(WSAGetLastError());
            if (hr == HRESULT_FROM_WIN32(WSAECONNREFUSED))
            {
                // WSAECONNREFUSED means no application listen on the given port.
                // This is not a failure. Reset the hresult to S_OK and return fReady to false
                hr = S_OK;
            }
            goto Finished;
        }
        *pfReady = TRUE;
    }

Finished:

    if (socketCheck != INVALID_SOCKET)
    {
        iResult = closesocket(socketCheck);
        if (iResult == SOCKET_ERROR)
        {
            hr = HRESULT_FROM_WIN32(WSAGetLastError());
        }
        socketCheck = INVALID_SOCKET;
    }

    if (pTCPInfo != NULL)
    {
        HeapFree(GetProcessHeap(), 0, pTCPInfo);
        pTCPInfo = NULL;
    }

    return hr;
}

// send signal to the process to let it gracefully shutdown
// if the process cannot shutdown within given time, terminate it
VOID
SERVER_PROCESS::SendSignal(
    VOID
)
{
    HRESULT hr      = S_OK;
    HANDLE  hThread = NULL;

    ReferenceServerProcess();

    m_hShutdownHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId);

    if (m_hShutdownHandle == NULL)
    {
        // since we cannot open the process. let's terminate the process
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    hThread = CreateThread(
        NULL,       // default security attributes
        0,          // default stack size
        (LPTHREAD_START_ROUTINE)SendShutDownSignal,
        this,       // thread function arguments
        0,          // default creation flags
        NULL);      // receive thread identifier

    if (hThread == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    //
    // Reset the shutdown timeout if debugger is attached.
    // Do it only for the case that debugger is attached during process creation
    // as IsDebuggerIsAttached call is too heavy
    //
    if (WaitForSingleObject(m_hShutdownHandle, m_fDebuggerAttached ? INFINITE : m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0)
    {
        hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
        goto Finished;
    }
    // thread should already exit
    CloseHandle(hThread);
    hThread = NULL;

Finished:
    if (hThread != NULL)
    {
        // if the send shutdown message thread is still running, terminate it
        DWORD dwThreadStatus = 0;
        if (GetExitCodeThread(hThread, &dwThreadStatus)!= 0 && dwThreadStatus == STILL_ACTIVE)
        {
            TerminateThread(hThread, STATUS_CONTROL_C_EXIT);
        }
        CloseHandle(hThread);
        hThread = NULL;
    }

    if (FAILED_LOG(hr))
    {
        TerminateBackendProcess();
    }

    if (m_hShutdownHandle != NULL && m_hShutdownHandle != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hShutdownHandle);
        m_hShutdownHandle = NULL;
    }

    DereferenceServerProcess();
}

//
// StopProcess is only called if process crashes OR if the process
// creation failed and calling this counts towards RapidFailCounts.
//
VOID
SERVER_PROCESS::StopProcess(
    VOID
)
{
    m_fReady = FALSE;

    m_pProcessManager->IncrementRapidFailCount();

    for (INT i=0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
    {
        if (m_hChildProcessHandles[i] != NULL)
        {
            if (m_hChildProcessHandles[i] != INVALID_HANDLE_VALUE)
            {
                TerminateProcess(m_hChildProcessHandles[i], 0);
                CloseHandle(m_hChildProcessHandles[i]);
            }
            m_hChildProcessHandles[i] = NULL;
            m_dwChildProcessIds[i] = 0;
        }
    }

    if (m_hProcessHandle != NULL)
    {
        if (m_hProcessHandle != INVALID_HANDLE_VALUE)
        {
            TerminateProcess(m_hProcessHandle, 0);
            CloseHandle(m_hProcessHandle);
        }
        m_hProcessHandle = NULL;
    }
}

BOOL
SERVER_PROCESS::IsDebuggerIsAttached(
    VOID
)
{
    HRESULT                             hr = S_OK;
    PJOBOBJECT_BASIC_PROCESS_ID_LIST    processList = NULL;
    DWORD                               dwPid = 0;
    DWORD                               dwWorkerProcessPid = 0;
    DWORD                               cbNumBytes = 1024;
    DWORD                               dwRetries = 0;
    DWORD                               dwError = NO_ERROR;
    BOOL                                fDebuggerPresent = FALSE;

    dwWorkerProcessPid = GetCurrentProcessId();

    do
    {
        dwError = NO_ERROR;

        if (processList != NULL)
        {
            HeapFree(GetProcessHeap(), 0, processList);
            processList = NULL;

            // resize
            cbNumBytes = cbNumBytes * 2;
        }

        processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc(
                            GetProcessHeap(),
                            0,
                            cbNumBytes
                            );
        if (processList == NULL)
        {
            hr = E_OUTOFMEMORY;
            goto Finished;
        }

        RtlZeroMemory(processList, cbNumBytes);

        if (!QueryInformationJobObject(
                m_hJobObject,
                JobObjectBasicProcessIdList,
                processList,
                cbNumBytes,
                NULL))
        {
            dwError = GetLastError();
            if (dwError != ERROR_MORE_DATA)
            {
                hr = HRESULT_FROM_WIN32(dwError);
                goto Finished;
            }
        }

    } while (dwRetries++ < 5 &&
             processList != NULL &&
             (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList ||
              processList->NumberOfProcessIdsInList == 0));

    if (dwError == ERROR_MORE_DATA)
    {
        hr = E_OUTOFMEMORY;
        // some error
        goto Finished;
    }

    if (processList == NULL ||
        (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList ||
        processList->NumberOfProcessIdsInList == 0))
    {
        hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED);
        // some error
        goto Finished;
    }

    if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES)
    {
        hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED);
        goto Finished;
    }

    for (DWORD i=0; i<processList->NumberOfProcessIdsInList; i++)
    {
        dwPid = (DWORD)processList->ProcessIdList[i];
        if (dwPid != dwWorkerProcessPid)
        {
            HANDLE hProcess = OpenProcess(
                    PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE,
                    FALSE,
                    dwPid);

            BOOL returnValue = CheckRemoteDebuggerPresent(hProcess, &fDebuggerPresent);
            if (hProcess != NULL)
            {
                CloseHandle(hProcess);
                hProcess = NULL;
            }

            if (!returnValue)
            {
                goto Finished;
            }

            if (fDebuggerPresent)
            {
                break;
            }
        }
    }

Finished:

    if (processList != NULL)
    {
        HeapFree(GetProcessHeap(), 0, processList);
    }

    return fDebuggerPresent;
}

HRESULT
SERVER_PROCESS::GetChildProcessHandles(
    VOID
)
{
    HRESULT                             hr = S_OK;
    PJOBOBJECT_BASIC_PROCESS_ID_LIST    processList = NULL;
    DWORD                               dwPid = 0;
    DWORD                               dwWorkerProcessPid = 0;
    DWORD                               cbNumBytes = 1024;
    DWORD                               dwRetries = 0;
    DWORD                               dwError = NO_ERROR;

    dwWorkerProcessPid = GetCurrentProcessId();

    do
    {
        dwError = NO_ERROR;

        if (processList != NULL)
        {
            HeapFree(GetProcessHeap(), 0, processList);
            processList = NULL;

            // resize
            cbNumBytes = cbNumBytes * 2;
        }

        processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc(
                            GetProcessHeap(),
                            0,
                            cbNumBytes
                            );
        if (processList == NULL)
        {
            hr = E_OUTOFMEMORY;
            goto Finished;
        }

        RtlZeroMemory(processList, cbNumBytes);

        if (!QueryInformationJobObject(
                m_hJobObject,
                JobObjectBasicProcessIdList,
                processList,
                cbNumBytes,
                NULL))
        {
            dwError = GetLastError();
            if (dwError != ERROR_MORE_DATA)
            {
                hr = HRESULT_FROM_WIN32(dwError);
                goto Finished;
            }
        }

    } while (dwRetries++ < 5 &&
             processList != NULL &&
             (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0));

    if (dwError == ERROR_MORE_DATA)
    {
        hr = E_OUTOFMEMORY;
        // some error
        goto Finished;
    }

    if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0))
    {
        hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED);
        // some error
        goto Finished;
    }

    if (processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES)
    {
        hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED);
        goto Finished;
    }

    for (DWORD i=0; i<processList->NumberOfProcessIdsInList; i++)
    {
        dwPid = (DWORD)processList->ProcessIdList[i];
        if (dwPid != m_dwProcessId &&
            dwPid != dwWorkerProcessPid )
        {
            m_hChildProcessHandles[m_cChildProcess] = OpenProcess(
                                            PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE,
                                            FALSE,
                                            dwPid
                                        );
            m_dwChildProcessIds[m_cChildProcess] = dwPid;
            m_cChildProcess ++;
        }
    }

Finished:

    if (processList != NULL)
    {
        HeapFree(GetProcessHeap(), 0, processList);
    }

    return hr;
}

HRESULT
SERVER_PROCESS::StopAllProcessesInJobObject(
        VOID
)
{
    HRESULT                             hr = S_OK;
    PJOBOBJECT_BASIC_PROCESS_ID_LIST    processList = NULL;
    HANDLE                              hProcess = NULL;
    DWORD                               dwWorkerProcessPid = 0;
    DWORD                               cbNumBytes = 1024;
    DWORD                               dwRetries = 0;

    dwWorkerProcessPid = GetCurrentProcessId();

    do
    {
        if (processList != NULL)
        {
            HeapFree(GetProcessHeap(), 0, processList);
            processList = NULL;

            // resize
            cbNumBytes = cbNumBytes * 2;
        }

        processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc(
                            GetProcessHeap(),
                            0,
                            cbNumBytes
                            );
        if (processList == NULL)
        {
            hr = E_OUTOFMEMORY;
            goto Finished;
        }

        RtlZeroMemory(processList, cbNumBytes);

        if (!QueryInformationJobObject(
                m_hJobObject,
                JobObjectBasicProcessIdList,
                processList,
                cbNumBytes,
                NULL))
        {
            DWORD dwError = GetLastError();
            if (dwError != ERROR_MORE_DATA)
            {
                hr = HRESULT_FROM_WIN32(dwError);
                goto Finished;
            }
        }

    } while (dwRetries++ < 5 &&
             processList != NULL &&
             (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0));

    if (processList == NULL || (processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0))
    {
        hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
        // some error
        goto Finished;
    }

    for (DWORD i=0; i<processList->NumberOfProcessIdsInList; i++)
    {
        if (dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i])
        {
            hProcess = OpenProcess(PROCESS_TERMINATE,
                                   FALSE,
                                   (DWORD)processList->ProcessIdList[i]);
            if (hProcess != NULL)
            {
                if (!TerminateProcess(hProcess, 1))
                {
                    hr = HRESULT_FROM_WIN32(GetLastError());
                }
                else
                {
                    WaitForSingleObject(hProcess, INFINITE);
                }

                if (hProcess != NULL)
                {
                    CloseHandle(hProcess);
                    hProcess = NULL;
                }
            }
        }
    }

Finished:

    if (processList != NULL)
    {
        HeapFree(GetProcessHeap(), 0, processList);
    }

    return hr;
}

SERVER_PROCESS::SERVER_PROCESS() :
    m_cRefs(1),
    m_hProcessHandle(NULL),
    m_hProcessWaitHandle(NULL),
    m_dwProcessId(0),
    m_cChildProcess(0),
    m_fReady(FALSE),
    m_lStopping(0L),
    m_hStdoutHandle(NULL),
    m_fStdoutLogEnabled(FALSE),
    m_hJobObject(NULL),
    m_pForwarderConnection(NULL),
    m_dwListeningProcessId(0),
    m_hListeningProcessHandle(NULL),
    m_hShutdownHandle(NULL),
    m_hStdErrWritePipe(NULL),
    m_hReadThread(nullptr),
    m_randomGenerator(std::random_device()())
{
    //InterlockedIncrement(&g_dwActiveServerProcesses);

    for (INT i=0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
    {
        m_dwChildProcessIds[i] = 0;
        m_hChildProcessHandles[i] = NULL;
        m_hChildProcessWaitHandles[i] = NULL;
    }
}

VOID
SERVER_PROCESS::CleanUp()
{
    if (m_hProcessWaitHandle != NULL)
    {
        UnregisterWait(m_hProcessWaitHandle);
        m_hProcessWaitHandle = NULL;
    }

    for (INT i = 0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
    {
        if (m_hChildProcessWaitHandles[i] != NULL)
        {
            UnregisterWait(m_hChildProcessWaitHandles[i]);
            m_hChildProcessWaitHandles[i] = NULL;
        }
    }

    if (m_hProcessHandle != NULL)
    {
        if (m_hProcessHandle != INVALID_HANDLE_VALUE)
        {
            TerminateProcess(m_hProcessHandle, 1);
            CloseHandle(m_hProcessHandle);
        }
        m_hProcessHandle = NULL;
    }

    if (m_hListeningProcessHandle != NULL)
    {
        if (m_hListeningProcessHandle != INVALID_HANDLE_VALUE)
        {
            CloseHandle(m_hListeningProcessHandle);
        }
        m_hListeningProcessHandle = NULL;
    }

    for (INT i = 0; i<MAX_ACTIVE_CHILD_PROCESSES; ++i)
    {
        if (m_hChildProcessHandles[i] != NULL)
        {
            if (m_hChildProcessHandles[i] != INVALID_HANDLE_VALUE)
            {
                TerminateProcess(m_hChildProcessHandles[i], 1);
                CloseHandle(m_hChildProcessHandles[i]);
            }
            m_hChildProcessHandles[i] = NULL;
            m_dwChildProcessIds[i] = 0;
        }
    }

    if (m_hJobObject != NULL)
    {
        if (m_hJobObject != INVALID_HANDLE_VALUE)
        {
            CloseHandle(m_hJobObject);
        }
        m_hJobObject = NULL;
    }

    if (m_pForwarderConnection != NULL)
    {
        m_pForwarderConnection->DereferenceForwarderConnection();
        m_pForwarderConnection = NULL;
    }

}

SERVER_PROCESS::~SERVER_PROCESS()
{
    DWORD    dwThreadStatus = 0;

    CleanUp();

    // no need to free m_pEnvironmentVarTable, as it references to
    // the same hash table hold by configuration.
    // the hashtable memory will be freed once onfiguration got recycled

    if (m_pProcessManager != NULL)
    {
        m_pProcessManager->DereferenceProcessManager();
        m_pProcessManager = NULL;
    }

    if (m_hStdErrWritePipe != NULL)
    {
        if (m_hStdErrWritePipe != INVALID_HANDLE_VALUE)
        {
            FlushFileBuffers(m_hStdErrWritePipe);
            CloseHandle(m_hStdErrWritePipe);
        }

        m_hStdErrWritePipe = NULL;
    }

    // Forces ReadFile to cancel, causing the read loop to complete.
    // Don't check return value as IO may or may not be completed already.
    if (m_hReadThread != nullptr)
    {
        LOG_INFO(L"Canceling standard stream pipe reader.");
        CancelSynchronousIo(m_hReadThread);
    }

    // GetExitCodeThread returns 0 on failure; thread status code is invalid.
    if (m_hReadThread != nullptr &&
        !LOG_LAST_ERROR_IF(!GetExitCodeThread(m_hReadThread, &dwThreadStatus)) &&
        dwThreadStatus == STILL_ACTIVE)
    {
        // Wait for graceful shutdown, i.e., the exit of the background thread or timeout
        if (WaitForSingleObject(m_hReadThread, PIPE_OUTPUT_THREAD_TIMEOUT) != WAIT_OBJECT_0)
        {
            // If the thread is still running, we need kill it first before exit to avoid AV
            if (!LOG_LAST_ERROR_IF(GetExitCodeThread(m_hReadThread, &dwThreadStatus) == 0) &&
                dwThreadStatus == STILL_ACTIVE)
            {
                LOG_WARN(L"Thread reading stdout/err hit timeout, forcibly closing thread.");
                TerminateThread(m_hReadThread, STATUS_CONTROL_C_EXIT);
            }
        }
    }

    if (m_hReadThread != nullptr)
    {
        CloseHandle(m_hReadThread);
        m_hReadThread = nullptr;
    }

    if (m_hStdoutHandle != NULL)
    {
        if (m_hStdoutHandle != INVALID_HANDLE_VALUE)
        {
            CloseHandle(m_hStdoutHandle);
        }
        m_hStdoutHandle = NULL;
    }

    if (m_fStdoutLogEnabled)
    {
        m_Timer.CancelTimer();
    }

    if (!m_fStdoutLogEnabled && !m_struFullLogFile.IsEmpty())
    {
        WIN32_FIND_DATA fileData;
        HANDLE handle = FindFirstFile(m_struFullLogFile.QueryStr(), &fileData);
        if (handle != INVALID_HANDLE_VALUE &&
            fileData.nFileSizeHigh == 0 &&
            fileData.nFileSizeLow == 0)
        {
            FindClose(handle);
            // no need to check whether the deletion succeeds
            // as nothing can be done
            DeleteFile(m_struFullLogFile.QueryStr());
        }
    }
}

//static
VOID
CALLBACK
SERVER_PROCESS::ProcessHandleCallback(
    _In_ PVOID  pContext,
    _In_ BOOL
)
{
    SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*) pContext;
    pServerProcess->HandleProcessExit();
}

HRESULT
SERVER_PROCESS::RegisterProcessWait(
    PHANDLE                 phWaitHandle,
    HANDLE                  hProcessToWaitOn
)
{
    HRESULT     hr = S_OK;
    NTSTATUS    status = 0;

    _ASSERT(phWaitHandle != NULL && *phWaitHandle == NULL);

    *phWaitHandle = NULL;

    // wait thread will dereference.
    ReferenceServerProcess();

    status = RegisterWaitForSingleObject(
                phWaitHandle,
                hProcessToWaitOn,
                (WAITORTIMERCALLBACKFUNC)&ProcessHandleCallback,
                this,
                INFINITE,
                WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD
                );

    if (status < 0)
    {
        hr = HRESULT_FROM_NT(status);
        goto Finished;
    }

Finished:

    if (FAILED_LOG(hr))
    {
        *phWaitHandle = NULL;
        DereferenceServerProcess();
    }

    return hr;
}

VOID
SERVER_PROCESS::HandleProcessExit( VOID )
{
    BOOL        fReady = FALSE;
    DWORD       dwProcessId = 0;

    if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L)
    {
        CheckIfServerIsUp(m_dwPort, &dwProcessId, &fReady);

        if (!fReady)
        {
            EventLog::Info(
                ASPNETCORE_EVENT_PROCESS_SHUTDOWN,
                ASPNETCORE_EVENT_PROCESS_SHUTDOWN_MSG,
                m_struAppFullPath.QueryStr(),
                m_struPhysicalPath.QueryStr(),
                m_dwProcessId,
                m_dwPort);

            m_pProcessManager->ShutdownProcess(this);
        }

        DereferenceServerProcess();
    }
}

HRESULT
SERVER_PROCESS::SendShutdownHttpMessage( VOID )
{
    HRESULT    hr = S_OK;
    HINTERNET  hSession = NULL;
    HINTERNET  hConnect = NULL;
    HINTERNET  hRequest = NULL;

    STACK_STRU(strHeaders, 256);
    STRU       strAppToken;
    STRU       strUrl;
    DWORD      dwStatusCode = 0;
    DWORD      dwSize = sizeof(dwStatusCode);

    hSession = WinHttpOpen(L"",
        WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
        WINHTTP_NO_PROXY_NAME,
        WINHTTP_NO_PROXY_BYPASS,
        0);

    if (hSession == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    hConnect = WinHttpConnect(hSession,
        L"127.0.0.1",
        (USHORT)m_dwPort,
        0);

    if (hConnect == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }
    if (m_struAppVirtualPath.QueryCCH() > 1)
    {
        // app path size is 1 means site root, i.e., "/"
        // we don't want to add duplicated '/' to the request url
        // otherwise the request will fail
        strUrl.Copy(m_struAppVirtualPath);
    }
    strUrl.Append(L"/iisintegration");

    hRequest = WinHttpOpenRequest(hConnect,
        L"POST",
        strUrl.QueryStr(),
        NULL,
        WINHTTP_NO_REFERER,
        NULL,
        0);

    if (hRequest == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    // set timeout
    if (!WinHttpSetTimeouts(hRequest,
        m_dwShutdownTimeLimitInMS,  // dwResolveTimeout
        m_dwShutdownTimeLimitInMS,  // dwConnectTimeout
        m_dwShutdownTimeLimitInMS,  // dwSendTimeout
        m_dwShutdownTimeLimitInMS)) // dwReceiveTimeout
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    // set up the shutdown headers
    if (FAILED_LOG(hr = strHeaders.Append(L"MS-ASPNETCORE-EVENT:shutdown \r\n")) ||
        FAILED_LOG(hr = strAppToken.Append(L"MS-ASPNETCORE-TOKEN:")) ||
        FAILED_LOG(hr = strAppToken.AppendA(m_straGuid.QueryStr())) ||
        FAILED_LOG(hr = strHeaders.Append(strAppToken.QueryStr())))
    {
        goto Finished;
    }

    if (!WinHttpSendRequest(hRequest,
        strHeaders.QueryStr(),  // pwszHeaders
        strHeaders.QueryCCH(),  // dwHeadersLength
        WINHTTP_NO_REQUEST_DATA,
        0,   // dwOptionalLength
        0,   // dwTotalLength
        0))  // dwContext
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    if (!WinHttpReceiveResponse(hRequest , NULL))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    if (!WinHttpQueryHeaders(hRequest,
        WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
        WINHTTP_HEADER_NAME_BY_INDEX,
        &dwStatusCode,
        &dwSize,
        WINHTTP_NO_HEADER_INDEX))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Finished;
    }

    if (dwStatusCode != 202)
    {
        // not expected http status
        hr = E_FAIL;
    }

    // log
    EventLog::Info(
        ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST,
        ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG,
        m_dwProcessId,
        dwStatusCode);

Finished:
    if (hRequest)
    {
        WinHttpCloseHandle(hRequest);
        hRequest = NULL;
    }
    if (hConnect)
    {
        WinHttpCloseHandle(hConnect);
        hConnect = NULL;
    }
    if (hSession)
    {
        WinHttpCloseHandle(hSession);
        hSession = NULL;
    }
    return hr;
}

//static
VOID
SERVER_PROCESS::SendShutDownSignal(
    LPVOID lpParam
)
{
    SERVER_PROCESS* pThis = static_cast<SERVER_PROCESS *>(lpParam);
    DBG_ASSERT(pThis);
    pThis->SendShutDownSignalInternal();
}

//
// send shutdown message first, if fail then send
// ctrl-c to the backend process to let it gracefully shutdown
//
VOID
SERVER_PROCESS::SendShutDownSignalInternal(
    VOID
)
{
    ReferenceServerProcess();

    if (FAILED_LOG(SendShutdownHttpMessage()))
    {
        //
        // failed to send shutdown http message
        // try send ctrl signal
        //
        HWND  hCurrentConsole = NULL;
        BOOL  fFreeConsole = FALSE;
        hCurrentConsole = GetConsoleWindow();
        if (hCurrentConsole)
        {
            // free current console first, as we may have one, e.g., hostedwebcore case
            fFreeConsole = FreeConsole();
        }

        if (AttachConsole(m_dwProcessId))
        {
            // As we called CreateProcess with CREATE_NEW_PROCESS_GROUP
            // call ctrl-break instead of ctrl-c as child process ignores ctrl-c
            if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId))
            {
                // failed to send the ctrl signal. terminate the backend process immediately instead of waiting for timeout
                TerminateBackendProcess();
            }
            FreeConsole();

            if (fFreeConsole)
            {
                // IISExpress and hostedwebcore w3wp run as background process
                // have to attach console back to ensure post app_offline scenario still works
                AttachConsole(ATTACH_PARENT_PROCESS);
            }
        }
        else
        {
            // terminate the backend process immediately instead of waiting for timeout
            TerminateBackendProcess();
        }
    }

    DereferenceServerProcess();
}

VOID
SERVER_PROCESS::TerminateBackendProcess(
    VOID
)
{
    if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L)
    {
        // backend process will be terminated, remove the waitcallback
        if (m_hProcessWaitHandle != NULL)
        {
            UnregisterWait(m_hProcessWaitHandle);

            // as we skipped process exit callback (ProcessHandleCallback),
            // need to dereference the object otherwise memory leak
            DereferenceServerProcess();

            m_hProcessWaitHandle = NULL;
        }

        // cannot gracefully shutdown or timeout, terminate the process
        if (m_hProcessHandle != NULL && m_hProcessHandle != INVALID_HANDLE_VALUE)
        {
            TerminateProcess(m_hProcessHandle, 0);
            m_hProcessHandle = NULL;
        }

        // log a warning for ungraceful shutdown
        EventLog::Warn(
            ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE,
            ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG,
            m_dwProcessId);
    }
}


Camphor and eucalyptus steam in the living room today with kids and quilts strewn about and a mischievous and giddy almost one year old mercilessly teasing those down for the count. That was the set up all of today as a bunch of our group battles cold and flu symptoms.
There was cereal for breakfast and slow chores being done with lots of help from the un-sick kids. A friend stopped over with a Cricut she had the fabulous idea to swap. Sean is making her a wood bowl and Annaliese will get the Cricut for Christmas.
Benjamin had the boogers and crud earlier in the week and it has sadly made its way through. I spent Benjamin's short morning nap indulging in a new hobby - soap making! I made a batch using our tallow, shea butter, castor oil, olive oil, and coconut oil and scented it with lavender. It is wrapped in towels and aprons on the kitchen island for the night and tomorrow I'll unmold it and stack it on a drying rack in our closet. This batch was a creamy yellow color.
Throughout the day we listened to Adventures in Odyssey and Your Story Hour, watched Thomas, Mr. Popper's Penguins, and James Herriot (on Netflix!).
I handed out elderberry soda (elderberry juice, seltzer and a pinch of sugar or honey).
A few of the kids made their way upstairs to play with clay and play dough. I worked on new pages for my home binder and vacuumed.
Roofers showed up for another day's work putting a new metal roof on our garage and a few smaller roofs. We managed to get all of the exterior of this old house painted over the summer, which is slightly amazing considering the work we put in at the cabin before it sold and our trip to Florida. There is a mess of trim waiting to be done come spring, but between the new paint and new roof, we're looking quite respectable, could you turn a blind eye to the myriad of toys across our two acres.
I poked my head outside and saw that Henny Penny, the buff orpington is still hanging out on our front porch. She was brutally hen-pecked last week and rehabilitated whilst living in our kitchen for a few days. Back in summer days I made a salve of comfrey, chickweed, beeswax, olive oil and plantain and used that to heal her head. We have a small flock of Old English hens and roosters now and I've not figured out who the bully of the bunch is but she's the second red-head to get pecked so harshly and I can't help but take it personally.
Lunch was saltines with homemade peach jam, carrot sticks, popcorn, and tomato salad. A neighbor has a CSA organic farm and gave us a bushel of green tomatoes about a month ago. They've been lining our windowsills and ripening beautifully, yellow, red, purple and bright green. I think we may still be eating fresh tomatoes into December!
Andrew wrapped in a coat and boots to take a look out in the woodshed at the deer Sean brought home last night. I'm thankful for venison for the freezer.
Sean returned home from work around one, to begin his office work at home. He's switched things up a bit with work and we all enjoy seeing him at home more often, even if he's working and needs to be left undisturbed.
The kids like getting to take lunch up to him in his office and say, "hello". It is still a novelty having him home.
Speaking of home improvements, after living with painted, peeling layers of wall paper for six years, I bit the bullet and borrowed a steamer from a friend. It was a week of dirty, nasty work but the layers in the living room are gone with a fresh creamy coat of paint in their wake. We were expecting we'd find crumbling lathe and plaster underneath and were prepared to pay to have the walls drywalled or skim coated. It was a wonderful surprise to find sheetrock under the wallpaper layers. I was thrilled not to have plaster dust through my house.
This sweet one is my size now, or nearly so. Annaliese turned thirteen in September and I have just a few inches on her. It won't be long till I'm looking up to my firstborn. So crazy, I tell you. She's been busy writing songs, taking and editing photos, emailing friends and making jewelry to sell. She is such a wonderful girl and I love having a young lady in the house.
With the littles napping and the rest watching Mr. Popper, Aiden and I ran some errands. Sean and I usually take one or two children with us if we're running out on "our own". It is a great opportunity for intimate conversations and letting the kiddo shine with our individual attention. Today we stopped by Ollie's for some organic cereal ($1.89 box), browsed their book selection and checked the toy stores, as Aiden has it in his mind to buy a train for his younger brother.
Dinner tonight was a hearty chicken potpie with cheddar biscuit crust. I threw in lots of garlic to give everyone a boost. Lately, when we've had leftover chicken and stock, I have been canning it. It is so convenient, on days like this when my brain is stiff and numb from mothering, to not have to thaw out a mass of frozen-ness before making dinner.
Benjamin is usually content to crawl around the kitchen floor while I'm cooking, so long as I'm slipping him little tidbits of frozen pea or cheese when he makes the loop around the island back to me.
Sean had a meeting after dinner and while Thursdays are my usual mom's night here, I cancelled tonight in order not to spread whatever these lousy germs are.
Benjamin and Addie had baths in lavender and footies donned.
We all cuddled around the couch again, with library books and then worked our way into some of our own. One of our favorites is Yonnie Wondernose, a story about a curious little Amish boy who gets handed a lot of responsibility.
Sean usually reads to the kids each night, which provides me a little time for sewing or whatnot. He's recently worked through The Hobbit, On the Banks of Plum Creek, and is reading By the Shores of Silver Lake currently.
Stories read, laundry put away and another load started, the girls retired to their room with a librivox recording of The Swiss Family Robinson and boys to their room with sleepy heads and books. Benjamin still sleeps near us and he was quite happy to burrow down into covers tonight. Outside, white lights are twinkling on the porches and Sean is home safe again.
That is a sick day anyone would be jealous of!
Wonderful, precious picture of your day. I love it.
From the first time I found your blog I have felt a peace when I read it. You have a gift dear Hannah. I enjoyed this post so much. I totally forgot you even had sick little ones. I love days like this! I hope you all have a great weekend.


text:
"""
Supports tweet compliance. See https://developer.twitter.com/en/docs/tweets/compliance/overview.
That is, providing the most recent version of a tweet or removing unavailable (deleted or protected)
tweets.

Also useful for splitting out available tweets from unavailable tweets.

Example usage: python tweet_compliance.py test.txt > test.json 2> test_delete.txt

For each tweet in a list of tweets or tweet ids provided by standard input or contained in files,
looks up the current tweet state.

If a tweet is not available and tweet ids are provided, the tweet id is
output to standard error.

If a tweet is not available and tweets are provided, the (deleted) tweet is output to standard error.

Otherwise, the current tweet (i.e., the tweet retrieved from the API) is returned to standard out.

Ordering is not guaranteed.

Requires Twitter API keys provided in ~/.twarc or environment variables. (See twarc.py.)
"""
from __future__ import print_function

import json
import fileinput
import twarc
import sys
import logging

# Send logging to file instead of STDERR.
logging.basicConfig(
    filename="tweet_compliance.log",
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

t = twarc.Twarc()

def process_tweets(tweets):
    available_tweet_ids = set()
    # Hydrate the tweets.
    for tweet in t.hydrate(tweets.keys()):
        # Keep track of the tweet ids of the tweets that are available.
        available_tweet_ids.add(tweet["id_str"])
        # Print available tweets to STDOUT.
        print(json.dumps(tweet))

    # Find the unavailable tweets.
    for tweet_id, tweet in tweets.items():
        if tweet_id not in available_tweet_ids:
            # Print tweet or tweet id to STDERR
            if tweets[tweet_id]:
                print(json.dumps(tweets[tweet_id]), file=sys.stderr)
            else:
                print(tweet_id, file=sys.stderr)

tweets = {}
for line in (line.rstrip("\n") for line in fileinput.input()):
    # Add tweet or None to tweet map.
    tweet_id = line
    tweet = None
    if not line.isdigit():
        tweet = json.loads(line)
        tweet_id = tweet["id_str"]
    tweets[tweet_id] = tweet

    # When get to 100, process the tweets.
    if len(tweets) == 100:
        process_tweets(tweets)
        tweets.clear()

# Process any remaining tweets.
if tweets:
    process_tweets(tweets)

redpajama_set_name: RedPajamaGithub


text:
All Right Resurved
HALDA RIVER
The Halda River is the one and the only natural carp breeding ground in Bangladesh from where fertilized carp fish's (Catla catla, Labeo rohita, Labeo calbasu and Cirrhinus mrigala) eggs are collected by local fisherman and egg collectors during April to June almost every year for time immemorial. The collected eggs are hatched in the artificial mud-made scoop on the riverbank to produce carp fries. The fry from here are supplied to different regions of the country for aquaculture.
Natural view of the Halda River
Halda River is one of the major rivers in the South-East region of Bangladesh. It originates from Halda chora (হালদা ছড়া) at the area of 2 no. Patachora union in Ramgarh Upazila under Khagrachari District (Former Chittagong Hill Tracts), Bangladesh. It flows through Fatikchhari Upazila, Hathazari Upazila, Raozan Upazila and Chittagong Chandgaon Thana before falling into the Karnaphuli River. The 98 km long river has a very turbulent tributary, the Dhurung River, which joins at Sundarpore about 48.25 km downstream. The river is navigable by big boats 29 km into it (up to Nazirhat) and by small boats 16-24 km further (up to Narayanhat). The Halda river is also famous for breeding pure Indian major carps. This is the only pure Indian major carp breeding field of Bangladesh, perhaps in South Asia.
Different man-made and natural disasters like destruction of spawning ground by ox-bow cutting, massive quarry of sands from river bed illegally by a section of unscrupulous traders, pollution of the river by industrial waste, unplanned construction of a good number of sluice gates by the locals for irrigation purpose, indiscriminate catching and killing of the brood fishes and climate change in the river have posed a serious threat to the bio-diversity of the Halda. Due to these risk factors, the river that was once a haven for brood fishes for releasing their eggs has now turned into a quite dangerous one resulting in drastic fall in the availability of eggs and fish-fries of natural species as lesser number of brood fishes have come here for releasing eggs in the recent years.
BASIC INFORMATIONS
Name of the River Halda River
Location Chittagong
It originates from Halda chora (হালদা ছড়া) at the area of 2 no. Patachora union in Ramgarh Upazila under Khagrachari District (Former Chittagong Hill Tracts), Bangladesh.
Longitude/Latitude 22°28'56.09"N & 91°54'07.62"E
Total Length 98 km
Distribution/ Area
It flows through Fatikchhari, Hathazari and Raozan Upazilas and Chandgaon Thana, and falls into the Karnaphuli River near Kalurghat area.
Why Unique?
It is only one tidal river natural carp breeding ground in the world from where carp eggs arecollected by the local fisherman. After collecting the eggs, the fishermen keep the spawn in the artificial small earthen pond or "kua" on the riverbank where the fry are hatched within 18 hours (Tsai et al. 1981). It is a unique indigenous technology of local fishermen.
Number of Excavated Oxbow bends 11
Number of Canals 34
Spawn Area Garduara to Maduna Ghat
Number of Sluice Gate 12 (Lower Halda)
Main Canals / Tributaries 5
Number of Fish Species 71 & Extinct: 04
Number of Egg Collectors 1100
Number of Fishermen 1600
Last Updated : March 16, 2012 0:29 AM & Copyright © 2010 [Md. Manzoorul Kibria]

redpajama_set_name: RedPajamaCommonCrawl


Here you find information about our regular delivery times and announcements regarding exceptional deliverytimes.
We currently ship EXCLUSIVELY & WITHOUT EXCEPTION within the EU, to the US, CANADA and SWITZERLAND.
The period starts with the day the contract is concluded and, when paying by bank transfer, at the day after placing your order at the executing credit institute, provided it is a SEPA-transfer and the currency is EURO, otherwise the period starts 5 days after placing your order.
The delivery time ends with the last day of the period.
If the delivery time ends on a Saturday, Sunday or on a holiday, which is commonly accepted at the place of delivery, the delivery time ends on the following working day.
We currently ship EXCLUSIVELY & WITHOUT EXCEPTION within the EU, to the US, KANADA and SWITZERLAND.
If you order more than one product, which have different delivery times, we will ship all products together.


Executive vice president and general manager Albie Hecht has exited HLN. He originally joined the network as general manager and executive vice president in September 2013. He joined the network from Nickelodeon Entertainment, where he was a senior executive. Before that, Hecht had been a co-founded Viacom's Spike TV. 'Like' HLN on Facebook for news and sports highlights.


text:
Home News National college media keynote speaker discusses the future of print journalism
National college media keynote speaker discusses the future of print journalism
Picket Staff
Thursday's keynote speaker at the ACP/CMA National College Media Convention in Austin, Texas was Pulitzer Prize winning journalist, Ken Herman.
(AUSTIN, TEXAS) – The future of journalism is online as newspapers seek to cut printing costs, Pulitzer Prize winning journalist Ken Herman told student journalist gathered for the College Media Association convention.
"The goal is to get rid of the print edition of the paper," Herman said.
Herman said, noting printing and delivery costs are viewed as expendable.
"Newspapers were a little slow to realize the importance of the Internet," Herman said. "Print doesn't compare to the amount of people that can be reached by digital media.
It is all part of the transition, Herman said.
"There is more and better (news) covered online," Herman said.
Herman used the example that if a sporting event takes place late on Saturday evening the Sunday print edition is going to advise readers to see the story posted on the website.
Herman has seen many changes in the news business over his career and said that there are people in the younger generations that will never pick up a newspaper.
"Younger people will not read it," Herman said. "Ten to 15 years all newspapers will be moving online."
In the 80's and 90's they told us we needed to get more young people reading the paper, Herman said. "Total failure," he said.
Young people will grow older, graduate college, get married, raise children and begin paying more attention to the things around them, he said, noting that as people get older, they pay more attention to events. Yet that doesn't mean younger generations will pick up printed newspapers.
Herman concluded his address by fielding questions from the student journalists about his career and the difficulties experienced with transitioning to a digital format.
Herman began his journalism career in 1975 and worked for local papers, The Associated Press in various locations and then the American Statesman in Austin, Texas, as Capitol Bureau Chief.
Herman won the Pulitzer for meritorious public service in 1977, while working the Lufkin Daily News.
"We should expose ourselves to things we disagree with – makes us better people," Herman said.
Todd Bowman is a staff writer for The Picket. He can be reached at tbowma04@rams.shepherd.edu or on Twitter @todd_bowman87.
Ken Herman
Previous articleShepherd cuts budget after West Virginia pares spending
Next articleSouth Carolina flooding hits close to home for Shepherd football player
Shepherd parents seek more from university in wake of coach's departure
Shepherd mum on sexual assault charges against women's coach


As the primary breadwinner for my family, I worked full-time. I had two baby girls 18 months apart. In each case, I continued working until the day before I gave birth and returned to work a mere six weeks after. I committed myself to nursing my babies, even when I had to resort to expressing milk on the road.
It took a health crisis to make me realize I needed more of the introspective, receiving Feminine Energy in my life, although I didn't recognize it by that term at the time. I was so busy exercising my Masculine Energy and 'doing' all the time that I never allowed myself any time to just 'be' and ultimately it made me sick.
As the primary breadwinner for my family, I worked full-time. I had two baby girls 18 months apart. In each case, I continued working until the day before I gave birth and returned to work a mere six weeks after. I committed myself to nursing my babies, even when I had to resort to expressing milk on the road. And it wasn't unusual for my colleagues to stop by my office at noon to request that I be on a plane by five p.m. Under these circumstances, my resolve as a career woman and a mother became severely tested. But I carried on pushing through, determined to do it all. My Masculine Energy was in overdrive.
Not surprisingly, I felt utterly exhausted. Eventually, that exhaustion became unmanageable and I got really sick -- my glands were raw and my limbs so heavy that I could hardly lift them -- all I wanted to do was sleep. My doctor informed me I had mononucleosis and predicted a recovery period that ranged between three weeks and three months.
But after three months passed, my fatigue continued to pummel me. I returned to the doctor multiple times, but the lab tests revealed nothing. Although I didn't know it at the time, I had another two years of symptoms ahead of me. My friends suggested exercise, but a walk around the block left me exhausted rather than refreshed. I well remember the feeling of the glands in my body swelling and fiery sore.
I continued to work during this period, but concluded each day by collapsing on the couch, completely depleted. I declined social engagements in favour of sleep. My weakened immune system left me vulnerable to any cold or flu circulating the office, and when I inevitably caught that cold or flu, I'd revert back to the acute stages of my mono, sleeping for days in a darkened room -- any natural or artificial light produced horrific migraines. Crawling out of bed for a glass of orange juice so exhausted me that I would consequently sleep for another forty-eight hours.
Throughout the next couple of years, I fell into a routine of collapsing on the couch each day after work and declining all social engagements. I continued to visit my doctor, hoping with each new appointment that an explanation would be found. I regularly completed the same medical test; every box would be methodically ticked off and each ailment would come back negative. It was very disheartening. I knew I was sick, yet my tests incessantly declared me healthy. I remember my doctor telling me, "If you were a different kind of person, I'd suggest that this is all a result of depression, but I just don't believe that's the case". I agreed with her.
Finally, she sent me to be evaluated by the Infectious Diseases department at the University of Saskatchewan. After a series of tests and examinations, the doctors gave me an official diagnosis of Epstein Barr Syndrome. I was the first person in Saskatchewan to be diagnosed with the illness, which is a precursor to Chronic Fatigue Syndrome. Epstein Barr Syndrome was first chronicled in medical journals in January of 1983; I received my diagnosis in May of the same year.
Although my illness had been identified, the virus was still too new for doctors to adequately treat me. It was a good news bad news story -- I knew that my suffering was legitimate, but I lacked the ability to alleviate it. Desperate for relief, I went to the library and took out books on people who overcame terminal diseases, hoping to find a common thread weaved throughout their stories.
As it turns out, that common thread was meditation, the necessary tool for visualizing ourselves healthy. I had some university friends who learned Transcendental Meditation but the lessons cost $500, money I didn't have. I remember my friends returning from their retreat with a mantra and a rose; for that kind of money, I needed a guarantee for stronger returns. Instead, I made another trip to the library to borrow any and all books on meditation I could find; in 1985, this quest resulted in a grand total of five small books. Still, I managed to teach myself how to meditate.
Each day, I would sit for 20 minutes and clear my mind, allowing myself to 'just be'. I would then visualize my body strong, pure and healthy. It was amazing how quickly I received positive results. After only six weeks I was able to accept a social engagement without putting me back into the acute stages of my mono and after three months if someone in my office had a cold or flu I didn't catch it. I can't tell you how rewarding that was for me!
While I didn't feel that I had the time to meditate, I knew it was a matter of my health and strongly adhered to the practice. Knowing the consequences was a strong motivating factor. My husband, who was initially a skeptic, could see the positive results and became a believer. When he would see my energy flagging he would say, "Why don't you take some time and go upstairs to meditate. I think that would make you feel better". And of course, he was correct.
The wonderful thing is that I have adhered to the practice ever since. Each day I take some time to just be and receive all the powerful energy of the universe. It recharges me, calms me and reduces my anxiety. Meditation for your being is like a charger on your cell phone. Studies show that meditation increases alpha brain waves, which, incidentally, promote self-healing. Meditation was the tool that allowed me to really connect with my Feminine Energy at a time when I was completely out of balance.
If you are burning the candle at both ends it is a sign that you are exercising too much Masculine Energy and that can be personally destructive. Learn from me and bring some Feminine Energy into your life before you pay the heavy price and get sick. For me it was meditation, but it could be walking in nature, taking time for prayer or just opening your spirit to the beauty of what you see looking out the window. I encourage you to find a way to stop each day and reflect, be still and let the wonder of Feminine Energy come to you.


SNYTF KID'S FIRST! throughout the decade Southern Nevada Youth Tennis foundation. continues providing FREE tennis to the community. Our 5th Annual partnership with UNLV-Healthy Kids Festival Giving back to make sure any child that wants to participate has the support! FUN! FUN ! ! 2000 kids and counting attending each year.
BRACELETS FOR THE KID'S FIRST Mayor Carolyn Goodman support us!


Choose to cook comfortably with the kitchen towels and potholders that is enriched with the attractive hand block floral prints. Handle the utensils with great ease and use the cloth pot holders, preventing your hands and table from heat. As the face and the back of the quilted pot holder is made of cotton, it is very light in weight. It has cotton fill inside. The heat resistant pot holders have a string in the corner to hang it anywhere in the kitchen. It comes in the packaging of 2. Gentle wash separately in cold water.


text:
The Dollop
Dave Anthony | Gareth Reynolds
The Dollop is a podcast hosted since April 2014 by American comedians Dave Anthony and Gareth Reynolds. Each episode centers around an event or person from history selected for its humorousness or peculiarity. Typical episodes feature subjects from American history described by Anthony and commented on by Anthony and Reynolds
513 - Best of The Dollop
We play the best clips from the first 10 episodes of the podcast Sources Tour Dates Redbubble Merch See Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info. See omnystudio.com/listener for privacy information.
Governor Evan Mecham - Part two
Comedians Dave Anthony and Gareth Reynolds examine Arizona Governor Evan MechamSourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.
503 - Governor Evan Mecham - part 1
Comedians Dave Anthony and Gareth Reynolds examine Arizona Governor Evan Mecham SourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.
502 - Aimee Elizabeth McPherson - Live
Comedians Dave Anthony and Gareth Reynolds examine religious leader Aimee Elizabeth McPherson. Recorded live in Phoenix, Arizona.SourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.
501 - Eugene Debs part 2 w/guest Karen Kilgariff
Comedians Dave Anthony and Gareth Reynolds are joined by Karen Kilgariff to examine American socialist Eugene DebsSourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.
500 - Eugene Debs w/ guest Karen Kilgariff
500 - Eugene Debs part 1 w/ guest Karen Kilgariff
499 - John Dawson - Live
Comedians Dave Anthony and Gareth Reynolds examine Utah Territory Governor John Dawson. Recorded live in Salt Lake City. SourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.
498 - Madame Dumont
Comedians Dave Anthony and Gareth Reynolds the Wild West's Madame Dumont.SourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.
497 - Jeffrey Fowle
Comedians Dave Anthony and Gareth Reynolds examine traveler Jeffrey Fowle.SourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.
496 - The Essex
Comedians Dave Anthony and Gareth Reynolds examine the journey of The EssexSourcesTour DatesRedbubble MerchSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.


My testing show that from a properly closed connection all Temp Destinations are removed.
What do you mean when you say you kill the C++ Producer?
the c++ producer the temporary queue stays and I get no errors from any kind.


text:
A Fevers of the Mind Quick-9 Interview with Kevin Hibshman
with Kevin Hibshman:
Q1: When did you start writing and first influences?
Kevin: I started writing around the age of fourteen. I got into Patti Smith and the writers she named as influences: Rimbaud, William Burroughs, etc.
Q2: Who are your biggest influences today?
Kevin: Today my biggest influences are Diane diPrima, Joanne Kyger, Julia Vinograd, Maura O'Connor and David Lerner. Only one of them is still alive.
Q3: Where did you grow up and how did that influence your writing/art? Have any travels away from home influence your work?
Kevin: I grew up in a very small town in Pennsylvania. I was alienated as a kid and music and poetry offered me a way out without sacrificing who I am. I spent some time in New Jersey and I think I was influenced by the quick pace of life there.
Q4: What do you consider the most meaningful work you've done creatively so far?
Kevin: I have a new book coming out hopefully soon that I'm very proud of because it's the first book of story-like pieces I've attempted. Most of the book is autobiographical prose. It's based on memories from my childhood and teen years that were very important in shaping me as a writer and as a person.
Q5: Any pivotal moment when you knew you wanted to be a writer/artist?
Kevin: I think the first time an editor asked me for work and then published it, I was hooked. That's when I began to take it seriously.
Q6: Favorite activities to relax?
Kevin: Music and movies.
Q7: Any recent or forthcoming projects that you'd like to promote?
Kevin: I'll provide a link to my poetry web-zine: Fearless.
https://archive.org/details/fearless-75/mode/2up
Q8: What is a favorite line/stanza from a poem of yours or others? Favorite art piece?
Kevin: A quote from Diane diPrima: " The only war is the war against the imagination."
Q9: Who has helped you most with writing?
Kevin: I'd have to mention my friends throughout the years who are fellow editors and writers who have supported and encouraged me greatly: Dave and Ana Christy, C.F. Roberts, John Patrick Robbins and Scott Simmons.
http://ryethewhiskeyreview.blogspot.com/2021/01/
Poetry by Kevin Hibshman : Shaman Song
Wolfpack Contributor: Kevin Hibshman
Categorized as Interviews, Kevin Hibshman, Poetry, Writing, Short Stories, Wolfpack Contributor, writers Tagged Fearless, Kevin Hibshman, Poetry, Quick-9 Interview, writers
Joe Kidd says:
Kevin is a kind, generous, and creative soul brother //oo\\
Pingback: Poetry by Kevin Hibshman : Two Winters Ago – Fevers of the Mind
Pingback: Poetry by Kevin Hibshman : Shaman Song – Fevers of the Mind
A Fevers of the Mind Quick-9 Interview with David Estringel
A Fevers of the Mind Quick-9 Interview with Barton Smock


The WARM* Foundation is presenting for the first time, a series of events around the launching of the book Discordia by the Magnum photographer, Moises Saman. From 24th to 27th February 2016 the agnes b. gallery in the rue Dieu is hosting an exhibition bringing together a selection of images from Discordia. Photographs taken in the Middle East from 2011 to 2015 capture the personal memories of Moises Saman.


Jesus told us, his disciples, to make disciples of all nations. One of the most important values of this church is our mission to the world. It begins in our own community, and spreads overseas to every nation. Church members are involved in a variety of ministries. If it helps others in any way, we want to be a part of it! Here are just a few examples of how you can serve others at GracePoint.
Food Pantry-Every month, people gather at the church to offer free bags of groceries, a short time of prayer, and a friendly smile to those in need throughout our community. Quarterly, we also provide a sit down meal and fellowship, so that we can fill their stomachs and hearts at the same time.
The Nest-GracePoint supports the ministry at Changed Lives Christian Center by feeding the homeless on the first Thursday of odd numbered months.
Adult Mission Trips-Each year, we send out adult mission teams to meet the needs of folks in poverty stricken areas in the US and abroad. Places include the Morgan-Scott Project in upper east Tennessee, Reelfoot Rural Ministry in west Tennessee, various locations throughout Alabama, and Hospital Escuala in Tegucigalpa, Honduras. This is in addition to all the global missions supported by our giving to the United Methodist Church.
Youth Mission Teams-Every summer our youth embark on their own mission trip. They travel to local and regional sites to become immersed in what it means to be the hands of Christ.
Baby Blankets-Ladies from GP supply super soft, prayer filled baby blankets for the Regional NICU at Women's and Children's Center at UAB. These blankets provide more than just physical comfort-they carry with them the peace and presence of God to babies and parents in need.
Angel Tree-Every year at Christmas, GP offers clothes, toys, and food to needy families in our area.
God-honoring worship is something we strive for. Many church members get involved in our music ministry. Some play their instruments. Some are vocalists. Others are involved in the choir. These volunteers are an important part of the ministry of our church, using their talents and gifts to glorify God. Whether it's in our traditional worship or the modern one, there is a place for your talents to be used to bring glory to God!
Using media to spread the gospel is a privilege of this church. In an age of widespread technology, we have a stewardship to use these tools as best as possible for kingdom purposes. Soon, all of our weekly sermons will be uploaded to our website in video or audio formats. We also maintain an active Facebook and Instagram feed for additional church communication.
GracePoint believes in the power of prayer! We have been witnesses to amazing outpourings of God's power through our prayers as healing of body, mind, and spirit have taken place in our midst. Prayer Force was launched as a vessel to further this value in our church culture.
Sunday morning prayer walks prepare our church for the move of God's Holy Spirit. Prayer beads and pocket crosses are produced and offered to help remind people they are not alone and to guide their "conversation" with God. Prayer shawls comfort those who are sick, lonely, grieving, or afraid. And prayer squares join children in their homes, at doctor's visits, and in the hallways and classrooms of their schools.
Do you love children or youth? Maybe you will find your place to serve in one of our vibrant ministries to our younger folks.
All of our volunteers in children and youth ministry are fully trained in Safe Sanctuary procedures, back ground checked, and connected to the church family for at least 6 months-all so that our little ones and their parents have peace of mind that GracePoint is a safe place for them to learn and grow.
Birthdays and anniversaries are big days to celebrate! Members of GP try to make these days even more special by sending special notes and cards on these special days. Those who are sick, have missed church for an extended amount of time, and especially our first time guests are greeted as well with a simple gift in the "old fashioned" mail.