-1

I am writing an application in C++ that needs to grab a list of installed packages from Windows 10. I have found a way to dump it into a text file via Powershell script.

I launch the script like so:

system("start powershell.exe -windowstyle hidden Set-ExecutionPolicy RemoteSigned \n");
system("start powershell.exe -windowstyle hidden .//package_list_save.ps1");

where package_list_save.ps1 is the script that creates the .txt file. Here is the contents of package_list_save.ps1:

Start-Transcript -Path ts.txt -Force
Get-AppxPackage
Stop-transcript

My issue is that it appears like the C++ portion of the application will move on before the Powershell script has a chance to create the text file, causing the rest of my functions to fail, as they depend on the package list that would be inside the text file. Here's the part that behaves incorrectly:

void loadPackageList(string PATH) {

    string temp;//a string to be used for any temporary holding of values

    ifstream fs;//create an in-file-stream
    fs.open(PATH);

    //skip 17 lines (all the junk up front) 
    for (int i = 0; i < 17; ++i) {
        getline(fs, temp);
    }

    //clear out temp just in case.
    temp = "";

    string currentLine;
    string name;
    string packageName;
    bool isFramework;

    while (getline(fs, currentLine)) {

        //read: name
        if ((currentLine.length() > 4) && (currentLine.substr(0, 4) == "Name")) {

            //get the name
            name = currentLine.substr(20);

            //skip four more lines
            for (int i = 0; i < 4; ++i) {
                getline(fs, temp);
            }

            //grab the fullPackageName line
            getline(fs, currentLine);

            packageName = currentLine.substr(20);

            //skip a line
            getline(fs, temp);

            //get the isFramework line
            getline(fs, temp);

            if (temp.substr(20) == "False") {
                isFramework = false;
            } else {
                isFramework = true;
            }

            //store all the relevent details of the package as a tuple
            tuple<string, string, bool>packageData;
            get<0>(packageData) = name;
            get<1>(packageData) = packageName;
            get<2>(packageData) = isFramework;

            packages.push_back(packageData);
        }
    }

        //DEBUG:
        for (tuple<string, string, bool> tp: packages) {
            clog << "Name: "          << get<0>(tp) << endl;
            clog << "  PackageName: " << get<1>(tp) << endl;
            clog << "  IsFramework: " << get<2>(tp) << endl;
        }
 }

So far I have tried to do things like a while loop that only terminates when the text file is detected:

while (!isFileExists("thing.txt")) {
}

But this either results in it completely ignoring the while loop (and subsequently still causing failure) or getting stuck forever.

I've tried using detection methods like this (taken from here):

inline bool exists_test1 (const std::string& name) {
    if (FILE *file = fopen(name.c_str(), "r")) {
        fclose(file);
        return true;
    } else {
        return false;
    }   
}

But I still can't seem to get it to behave the way I want. Is there a better way for me to pause my C++ code until the Powershell finishes?

EDIT: Here is a minimal, verifiable example:

#include <fstream>
#include <iostream>
#include <windows.h>

using namespace std;

int main() {

    ofstream fs;
    fs.open("package_list_save.ps1");
    fs << "Start-Transcript -Path ts.txt -Force" << endl;
    fs << "Get-AppxPackage" << endl;
    fs << "Stop-transcript" << endl;
    fs.close();

    system("start powershell.exe -windowstyle hidden Set-ExecutionPolicy RemoteSigned \n");
    system("start powershell.exe -windowstyle hidden .//package_list_save.ps1");

    ifstream ifs;
    ifs.open("ts.txt");

    string currentLine;

    while (getline(ifs, currentLine)) {
        cout << currentLine << endl;
    }

    cout << "Done." << endl;
}

On first run, there is no output besides "Done", but on second run (assuming you don't delete ts.txt" there is a lot of output. I need to be able to get all that output on the first run and not make the user run my application twice.

CCD
  • 470
  • 1
  • 7
  • 19
  • "can't seem to get it to behave the way I want" - how _does_ it behave? – Mathias R. Jessen Jan 22 '18 at 18:42
  • "the C++ portion of the application will move on before the Powershell script has a chance to create the text file, causing the rest of my functions to fail". When I think about it more, it also may be that the .txt file is created but not populated with actual text by the powershell script for another 50ms or so. – CCD Jan 22 '18 at 18:43
  • 3
    @Aaron How are you calling your powershell script from your application? Using `system()` should block (from within the same thread). –  Jan 22 '18 at 18:44
  • 1
    What started the script? Another program? Have you tried opening the the file with exclusive access? – user4581301 Jan 22 '18 at 18:45
  • @user9212993 I have updated my original post body. – CCD Jan 22 '18 at 18:48
  • @user4581301 started from within my code. Please see the original post body for more details. – CCD Jan 22 '18 at 18:49
  • 2
    @Aaron And those `system()` calls and the `while()` loop are running in the same thread? Could you provide a [MCVE] please. –  Jan 22 '18 at 18:52
  • @user9212993 Added. See edit in main post. They are all in the same thread to my knowledge. – CCD Jan 22 '18 at 19:29

1 Answers1

1

What went wrong

system runs start (doc link). Start runs powershell and exits, allowing system to return. Program continues while powershell runs.

Dummy example:

int main() {
    system("start cmd");
    std::cout << "Done.";
} 

exits the program and kills the command prompt before you probably even get to see it. But

int main() {
    system("cmd");
    std::cout << "Done.";
} 

waits for the command prompt to be closed before continuing.

Solution 1

Remove the start. Based on the "-windowstyle hidden" in the command line this probably throws a bunch of crap you don't want into the console output, but is good for a proof of concept.

system("powershell.exe -windowstyle hidden Set-ExecutionPolicy  RemoteSigned \n");
system("powershell.exe -windowstyle hidden .//package_list_save.ps1");

Solution 2

Don't use system. Powershell suggests the asker's target OS is Windows, so we can use Windows system functions. In this case I'd CreateProcess (doc link) and invoke WaitForSingleObject (doc link) on the hProcess handle stored in the PROCESS_INFORMATION provided as lpProcessInformation to CreateProcess.

Usage details here: Using CreateProcess() to exit/close an opened Application

user4581301
  • 33,082
  • 7
  • 33
  • 54
  • I see. So basically "start" is a command that launches other things. Therefore, as soon as start finishes launching powershell it exists, allowing the C++ to continue on? – CCD Jan 22 '18 at 20:42
  • 1
    @Aaron you got it. – user4581301 Jan 22 '18 at 20:56
  • Okay cool. The reason I'm using system is that it just seemed simpler given my application is targeting specifically Windows 10 machines. – CCD Jan 22 '18 at 21:02
  • If you want simple, just use solution 1 and remove "start". But note: two system calls will be two separate powershells. I don't know powershell well enough to sure that the parameters set in the first powershell will persist into the second. – user4581301 Jan 22 '18 at 21:11