handmade.network » Forums » Getting the environment block of a child process in win32
mrmixer
Simon Anciaux
418 posts
#15487 Getting the environment block of a child process in win32
2 months, 2 weeks ago Edited by Simon Anciaux on June 4, 2018, 10:48 p.m. Reason: Initial post

Is there a way to get the environment block of a process created with CreateProcess ? More precisely I would like to use CreateProcess to call vcvarsall.bat and then being able to call cl.exe in another CreateProcess. I found this "solution" but it's more of a hack than a real solution.
This question is related to this 4coder thread.
mmozeiko
Mārtiņš Možeiko
1737 posts / 1 project
#15492 Getting the environment block of a child process in win32
2 months, 2 weeks ago Edited by Mārtiņš Možeiko on June 5, 2018, 12:39 a.m.

You can invoke "cmd.exe /c set" process which will print out its env variables. Capture stdout and store these values. Then when you want to launch new process, pass all of them to environment vars. There is no way to pass "environment block" between processes. Otherwise - what would happen if one would modify it? Other process could crash or read garbage data if variable is not available anymore and it is keeping pointer to it (from the time variable existed).
mrmixer
Simon Anciaux
418 posts
#15510 Getting the environment block of a child process in win32
2 months, 1 week ago

That's what was suggested on stackoverflow. I was hoping there was a more direct way to get a copy.
Here is the code that does it with some notes about the issues I ran into.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/* NOTE: build command: cl main.c -nologo -Zi -Od */
#include <windows.h>
#include <stdio.h>
#include <assert.h>

typedef enum Error {

    error_none,

    error_create_process,
    error_wait_for_process,
    error_path_environment_variable_not_found,
    error_path_could_not_be_set,
    error_mark_not_found,

} Error;

void printWindowsError( char* message, DWORD error ) {

    char buffer[ 1024 ];
    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
    FormatMessage( flags, 0, error, 0, buffer, 1024, 0 );
    printf( "%s%s\n", message, buffer );
}

int main( int argc, char* argv[ ] ) {

    Error error = 0;

    /* NOTE: Redirecting standard i/o:
    stdout, stdin and stderror are all required, you can't just specify one or the other will be INVALID_HANDLE_VALUE.
    Also you need to set inherit handles when creating the file and when creating the process. */
    SECURITY_ATTRIBUTES security = { 0 };
    security.nLength = sizeof( security );
    security.lpSecurityDescriptor = 0;
    security.bInheritHandle = 1;

    HANDLE std_out = CreateFile( "out.txt", GENERIC_READ | GENERIC_WRITE, 0, &security, CREATE_ALWAYS, 0, 0 );
    HANDLE std_in = GetStdHandle( STD_INPUT_HANDLE );
    HANDLE std_error = GetStdHandle( STD_ERROR_HANDLE );

    {
        printf( "Calling vcvarsall: " );

        PROCESS_INFORMATION processInformation = { 0 };

        STARTUPINFO startupInformation = { 0 };
        startupInformation.cb = sizeof( startupInformation );
        startupInformation.dwFlags = STARTF_USESTDHANDLES;
        startupInformation.hStdOutput = std_out;
        startupInformation.hStdInput = std_in;
        startupInformation.hStdError = std_error;

        /* NOTE: echo __env_start_mark__ because stdout will contain anything printed by vcvarsall and we want to know when the environment starts.
        Also if vcvarsall fails, the output will not contain the string because we used &&. */
        char command[ ] = "cmd /C \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x64 && echo __env_start_mark__ && set";
        BOOL inheritHandles = 1;

        if ( CreateProcessA( 0, command, 0, 0, inheritHandles, 0, 0, 0, &startupInformation, &processInformation ) ) {

            if ( processInformation.hProcess != 0 ) {

                DWORD waitStatus = WaitForSingleObject( processInformation.hProcess, INFINITE );

                if ( waitStatus != WAIT_OBJECT_0 ) {

                    error = error_wait_for_process;

                    if ( waitStatus == WAIT_FAILED ) {
                        printWindowsError( "FAILED.\nWait for process error: ", GetLastError( ) );
                    }

                } else {
                    printf( "SUCCESS.\n" );
                }
            }

            if ( processInformation.hProcess ) {
                CloseHandle( processInformation.hProcess );
            }

            if ( processInformation.hThread ) {
                CloseHandle( processInformation.hThread );
            }

        } else {
            error = error_create_process;
            printWindowsError( "FAILED.\nCreate process error: ", GetLastError( ) );
        }
    }

    char* envBlockStart = 0;

    if ( !error ) {

        printf( "Creating environment block: " );

        LARGE_INTEGER size;
        LARGE_INTEGER zero = { 0 };
        GetFileSizeEx( std_out, &size );
        SetFilePointerEx( std_out, zero, 0, FILE_BEGIN );

        /* NOTE: +1 to make sure we have space for the block end '0'.
        In practice we always have space if there is at least 2 environment variables because of the '\r'.*/
        char* file = malloc( size.QuadPart + 1 );
        DWORD read;
        ReadFile( std_out, file, size.LowPart, &read, 0 );
        CloseHandle( std_out );
        DeleteFile( "out.txt" );

        char* end = file + size.QuadPart;
        char* current = file;

        char* mark = "__env_start_mark__";
        size_t markLength = sizeof( "__env_start_mark__" ) - 1;
        int found = 0;

        while ( current + markLength  < end ) {

            if ( strncmp( mark, current, markLength ) == 0 ) {
                found = 1;
                break;
            } else {
                current++;
            }
        }

        if ( found ) {

            current += markLength;

            while ( current < end && *current != '\n' ) {
                current++;
            }

            current++;

            char* out = current;
            envBlockStart = out;

            /* NOTE: We need to remember the path environment variable. See below. */
            char* path = 0;
            char* pathString1 = "PATH=";
            char* pathString2 = "Path=";
            char* pathString3 = "path=";
            size_t pathStringLength = sizeof( "PATH=" ) - 1;

            while ( current < end ) {

                if ( *current == '\n' ) {

                    *out = 0;
                    out++;

                    if ( !path ) {

                        char* test = current + 1;

                        if ( test + pathStringLength < end && ( strncmp( test, pathString1, pathStringLength ) == 0 || strncmp( test, pathString2, pathStringLength ) == 0 || strncmp( test, pathString3, pathStringLength ) == 0 ) ) {
                            path = out;
                        }
                    }

                } else if ( *current == '\r' ) {
                } else {
                    *out = *current;
                    out++;
                }

                current++;
            }

            *out = 0;
            out++;

            size_t envBlockLength = out - envBlockStart;
            assert( envBlockLength <= 32767 );

            if ( path ) {

                path[ pathStringLength - 1 ] = 0;

                /* NOTE: We need to set the current process path so CreateProcess will search for cl.exe in Visual Studio's folders.*/
                if ( !SetEnvironmentVariable( path, path + pathStringLength ) ) {
                    error = error_path_could_not_be_set;
                    printWindowsError( "FAILED.\nCouldn't set the path environment variable: ", GetLastError( ) );
                } else {
                    path[ pathStringLength - 1 ] = '=';
                    printf( "SUCCESS.\nPath environment variable found and set.\n" );
                }

            } else {
                error = error_path_environment_variable_not_found;
                printf( "FAILED.\nCould not find the path environment variable.\n" );
            }

        } else {
            error = error_mark_not_found;
            printf( "FAILED.\nCould not find the mark.\n" );
        }
    }

    if ( !error ) {

        printf( "Calling cl:\n" );

        PROCESS_INFORMATION processInformation = { 0 };

        STARTUPINFO startupInformation = { 0 };
        startupInformation.cb = sizeof( startupInformation );

        char command[ ] = "cl";

        if ( CreateProcessA( 0, command, 0, 0, 0, 0, envBlockStart, 0, &startupInformation, &processInformation ) ) {

            if ( processInformation.hProcess != 0 ) {

                DWORD waitStatus = WaitForSingleObject( processInformation.hProcess, INFINITE );

                if ( waitStatus != WAIT_OBJECT_0 ) {

                    error = error_wait_for_process;

                    if ( waitStatus == WAIT_FAILED ) {
                        printWindowsError( "FAILED.\nWait for process error: ", GetLastError( ) );
                    }

                } else {
                    printf( "SUCCESS.\n" );
                }
            }

            if ( processInformation.hProcess ) {
                CloseHandle( processInformation.hProcess );
            }

            if ( processInformation.hThread ) {
                CloseHandle( processInformation.hThread );
            }

        } else {
            error = error_create_process;
            printWindowsError( "FAILED.\nCreate process error: ", GetLastError( ) );
        }
    }

    printf( "Press any key to exit.\n" );
    getchar( );

    return error;
}