Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SteamCMD] Optionally log update/install progress to file #1684

Open
Exigency opened this issue Mar 9, 2014 · 38 comments
Open

[SteamCMD] Optionally log update/install progress to file #1684

Exigency opened this issue Mar 9, 2014 · 38 comments
Labels

Comments

@Exigency
Copy link

Exigency commented Mar 9, 2014

For gameserver control panels moving to SteamCMD, it is impossible to monitor server install/update progress as SteamCMD has flakey STDOUT on Windows and *NIX. It would be a great addition to have a ConVar option to enable writing update progress to (an already existing?) SteamCMD logfile.

Just to be clear, I am talking about the highlighted part in the screenshot that would be written to logfiles so gameserver control panels can poll and show update progress to the end-user.

progress

As STDERR is already logged to a file by default, progress logging would make a valuable addition to SteamCMD.

@Exigency
Copy link
Author

Related: #352

@stevenh
Copy link

stevenh commented Aug 10, 2014

Digging around the issue could well be that stdout goes fully buffered when stdout isn't a tty. When this happens the buffer size is apparently 1 page size so likely 4k, which ties in with what I've seen when piping the output to a file for large downloads.

The fix could be as easy as adding the following to the code to enforce line buffering of stdout:

setvbuf(stdout, (char *)NULL, _IOLBF, 0);

This still doesn't explain the jumbled output.

@powerlord
Copy link

SteamCMD uses a number of log files. Not sure where it goes on Windows, but on Linux it does to ~/Steam/logs/ (Where ~ is the user's home directory)

I can see 5 logs sitting there right now:

-rwxrwxr-x 1 tf2 tf2 32074 Aug 26 15:18 appinfo_log.txt
-rwxrwxr-x 1 tf2 tf2  3752 Aug 26 15:20 bootstrap_log.txt
-rwxrwxr-x 1 tf2 tf2  4930 Aug 26 15:20 connection_log.txt
-rwxrwxr-x 1 tf2 tf2 43638 Aug 26 15:20 content_log.txt
-rw-rw-r-- 1 tf2 tf2     0 Aug 26 15:18 stderr.txt

Note that stderr.txt may be empty because I use @ShutdownOnFailedCommand 1 in my steamcmd script.

Note that none of these logs contain what you see in stdout, though.

@PhonicUK
Copy link

I don't think it's necessary to use a log file, just to not use such a large buffer for standard output so that it's output can be piped in a sane manner.

@alfred-valve
Copy link
Contributor

steamcmd already sets setvbuf() when creating its console window, but I notice that if it didn't need to create a console window (so depending on how it is launched) you may not be setup right. I'll fix that.

@HenryGarle
Copy link

@alfred-valve Glad to see a fix might be coming for this! Is it still on the cards? It would certainly make my life much easier.

@Gachl
Copy link

Gachl commented Feb 17, 2016

Has there been any updates yet? We're currently re-developing our server manager in C# where it seems not possible to get the console output from StandardOutput. That's kinda a big issue to us because we can't check for updates on an app or track the update status.

Process process = new Process();
process.StartInfo = new ProcessStartInfo()
{
    FileName = @"C:\steamcmd\steamcmd.exe",
    Arguments = "+login anonymous +app_info_update 1 +app_info_print 258550 +exit",
    RedirectStandardOutput = true,
    UseShellExecute = false
};
process.Start();
process.WaitForExit();
string statusStructure = process.StandardOutput.ReadToEnd();

The string statusStructure will only contain

Steam Console Client (c) Valve Corporation
-- type 'quit' to exit --
Loading Steam API...OK.

Connecting anonymously to Steam Public...Logged in OK
Waiting for license info...OK

but none of the relevant stuff.

@Tele42
Copy link
Contributor

Tele42 commented Feb 17, 2016

@dgibbs64 and my dirt simple updater works around this by deleting the local cache before trying to get the remote build version. See https://github.com/dgibbs64/linuxgsm/blob/master/functions/update_check.sh#L121 for linuxgsm's version and https://github.com/SteamLUG/steamlug-gaming-servers/wiki/GS5#update-script for the dirt simple updater.

@Gachl
Copy link

Gachl commented Feb 18, 2016

That didn't work for me. I've found a workaround of executing the same command a couple of times to force the buffer to flush.

+login anonymous +app_info_update 1 +app_info_print 258550 +app_info_print 258550 +exit

This gives me about one and a quarter of the results of the app_info_print command. It's not helping for tracking running updates though.

@PhonicUK
Copy link

PhonicUK commented Feb 18, 2017

The issue is that SteamCMD often isn't writing to standard output - under Windows it's writing data directly to the console buffer without using the standard IO streams. This means that in order to capture it's output programmatically you have to do some voodoo black magic with the Win32 console API.

It also outputs the results of things like +app_info_print in one huge write instead of a line at a time, which requires that you have to force the console to have a massive vertical buffer to capture all the output or you'll just end up with the last screen buffers worth of output.

It's the kind of thing I'd do if I were deliberately trying to prevent someone from reading the output programmatically while still being able to read it when executed by hand on a console. I have managed to work around it but it was a significant amount of work.

@zrisher
Copy link

zrisher commented Apr 10, 2017

The issue is that SteamCMD often isn't writing to standard output - under Windows it's writing data directly to the console buffer without using the standard IO streams.

Finally an explanation of the the root cause of this issue that makes sense.

Relates to #1929.

This means that in order to capture it's output programmatically you have to do some voodoo black magic with the Win32 console API... I have managed to work around it but it was a significant amount of work.

@PhonicUK If you managed to collect all the output from steamcmd, any chance you'd be willing to release that work as a wrapper executable?

@PhonicUK
Copy link

@zrisher Afraid not since its for my companies commercial control panel so we don't want other vendors to have the same advantage. We had to produce a complex console application that can create and manipulate a virtual console buffer (without an actual console window attached) that it then makes the 'misbehaving' application use, and then it scans the virtual console row by row to get the data and write it to standard output so other applications can read it normally. It's really really messy and uber-hacky but it works just barely well enough to get data out.

@gaddman
Copy link

gaddman commented Mar 16, 2018

This won't help for Windows, but at least in Linux you can capture with a dummy pty. Python code for example:

#!/usr/bin/env python3
# with help from https://stackoverflow.com/a/31968411/3592326
import os
import pty
import sys
import subprocess
output = ''
command = 'steamcmd +login anonymous +app_update 232370 +quit'
master, slave = pty.openpty()
p=subprocess.Popen(command.split(), stdout=slave)
os.close(slave)

while True:
	try:
		# read in a chunk of data
		data = os.read(master, 1024)
		output += data.decode('ascii')
		# print line and cut from output
		for line in output.splitlines():
			print(">>>" + line)
			output=output[len(line):].lstrip('\r\n')
	except OSError as e:
		print ("Finished")
		break

@bbigras
Copy link

bbigras commented Feb 26, 2019

@RyanTT
Copy link

RyanTT commented Jun 11, 2019

@bbigras Justed tested it, works with ConPTY, all output comes through as expected

@Foxlider
Copy link

@RyanTT Any chance of you releasing a sample code for implementation ?

@Napster653
Copy link

I solved this problem on a Node.js web app using node-pty.

I'll just leave a snippet of code here that'll hopefully help other people with the same problem.

The output is obtained line by line as it should.

const pty = require('node-pty');
const os = require('os');

const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
var ptyProcess = pty.spawn(shell, [], { handleFlowControl: true });

ptyProcess.on('data', function (data) {
	// Do something
});
ptyProcess.write('steamcmd.exe +login anonymous +app_update ###### validate +quit\r');

@bbigras
Copy link

bbigras commented Jun 11, 2020

Thanks @Napster653 .

It seems node-pty uses the new conpty if it's available or fallback to using the hackish winpty lib.

I tried both conpty and winpty with Rust and it seems to work fine.

If you use winpty and you need winpty-agent.exe, you can get it without having to build it yourself with scoop. It's in the extra bucket.

@Foxlider
Copy link

Foxlider commented Dec 7, 2020

For those who want, you can use winpty (the github example is here)
I've managed a near realtime output.

All you need is to find a way to filter out all the weird extra characters

@PhonicUK
Copy link

PhonicUK commented Dec 8, 2020

@Foxlider I think the extra characters are just ANSI control codes, so you can look at https://github.com/chalk/ansi-regex for an expression that can filter those out.

@Foxlider
Copy link

Foxlider commented Dec 9, 2020

Thanks a lot @PhonicUK !
Now the only thing left is to find a way to get the input texts as well such as SteamGuard Code request or Password Request...

@ClanHost
Copy link

ClanHost commented Nov 23, 2021

So I came across this comment looking to find an alternative to what I have been using for years... I thought I would share my code with you... Running on windows using latest version of XAMPP. I left in some useful info you may want to use.

image

@bbigras
Copy link

bbigras commented Nov 23, 2021

btw, it's better to share code with code blocks. So people can copy paste from it.

@ClanHost
Copy link

btw, it's better to share code with code blocks. So people can copy paste from it.

I did but when I used the BBCODE ` it is all messed up :) Lucky not too much there.

@LostBeard
Copy link

Still a problem 7 years after reported. The documentation claims steamcmd is designed to be used with automation but steamcmd is designed in a way that directly prohibits it.

The command below won't write any of the progress info until after the process exits making it impossible to work with interactively. I have spent hours working on how to read the Steam guard prompt, and installation/update progress and other output from steamcmd with little success due to the design choices of steamcmd.

steamcmd +login myusername mypassword +quit 1> _output.txt 2>&1

steamcmd's bypassing of the normal console workflow make it next to useless for automation.

Obviously Steam sees this as a non-issue or it would have been fixed by now. Maybe remove the "Automating SteamCMD" section from https://developer.valvesoftware.com/wiki/SteamCMD considering you don't really want it "automated".

@PhonicUK
Copy link

For Automation I don't think you're supposed to use it interactively. Everything you can enter into it in interactive mode can be supplied as command line flags - so you're expected to generate a full set of flags that does everything you're after. So you attempt to login, if the output indicates that it needs steam guard then you fully restart the process with the command line flag that includes the steam guard code.

@LostBeard
Copy link

LostBeard commented Dec 16, 2021

How are you suppose to see the Steam Guard request if steamcmd doesn't write it to console in a readable way? How is your automation software going to know to stop the process and add the required info? It can't. The prompt for input does not write to the console in a readable way.

I had to find a workaround that literally reads the characters from an app owned console window because stamcmd does not make requests for Steam Guard code readable.

An automated app will just hang, unable to read the "Steam Guard code:" line from steamcmd output because of the way steamcmd writes to the console when it requests input. Your automation app has no (standard) way of reading that steamcmd output to know that steamcmd wants something.

@LostBeard
Copy link

LostBeard commented Dec 16, 2021

To demonstrate run the command below to login (fake account name.) steamcmd will prompt for a password

steamcmd +login myusername +quit

Outputs to console:

Redirecting stderr to 'C:\EpochServer\steam\logs\stderr.txt'
Looks like steam didn't shutdown cleanly, scheduling immediate update check
[  0%] Checking for available updates...
[----] Verifying installation...
Steam Console Client (c) Valve Corporation - version 1637624355
-- type 'quit' to exit --
Loading Steam API...OK
Logging in user 'myusername' to Steam Public...
password:

Now run that same command but instead send the output to a text file (gets same output as an automatation script/app would)

steamcmd +login myusername +quit 1> passrequest.txt 2>&1

Outputs to passrequest.txt:

Steam Console Client (c) Valve Corporation - version 1637624355
-- type 'quit' to exit --
Loading Steam API...OK
Logging in user 'myusername' to Steam Public...

The output does not contain the password prompt (while steamcmd is waiting.) This is how it works for all prompts from steamcmd. They are not visible in the output while steamcmd is waiting for the input.

If you kill steamcmd (Ctrl+C while the above command is waiting) then... steamcmd will write the password prompt to the output file.

Steam Console Client (c) Valve Corporation - version 1637624355
-- type 'quit' to exit --
Loading Steam API...OK
Logging in user 'myusername' to Steam Public...
password: src\tier1\fileio.cpp (5086) : m_vecRegisteredWriters.Count() == 0
src\tier0\threadtools.cpp (3594) : Assertion Failed: Illegal termination of worker thread 'CFileWriterThread'
src\tier1\fileio.cpp (5146) : CFileWriterThread already exited
src\tier1\fileio.cpp (5146) : CFileWriterThread already exited
src\tier1\fileio.cpp (5146) : CFileWriterThread already exited
^C

@PhonicUK
Copy link

PhonicUK commented Dec 16, 2021

Ah apologies I forgot that steam guard is the exception where you have to be able to read it line-by-line to know if it's required and feed the input. We use a proprietary technique to prevent steamcmd from buffering it's output so our software can read each line as it comes and prompt the user to supply the code, and then subsequently feed the steam guard code in via stdin. I can't tell you exactly how we achieve this, only that it is possible on both Windows and Linux.

But that doesn't really address that this shouldn't actually be necessary.

@ScottKane
Copy link

ScottKane commented Jan 9, 2022

internal static class Program
{
    public static async Task Main()
    {
        using var process = Process.Start(new ProcessStartInfo
        {
            FileName = "steamcmd",
            Arguments = "+help +quit",
            UseShellExecute = false,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            CreateNoWindow = true
        });
        
        var buffer = new char[Console.BufferWidth];
        var outputRead = 1;
        var errorRead = 1;
        
        while (outputRead > 0 || errorRead > 0)
        {
            if (outputRead > 0)
            {
                outputRead = await process?.StandardOutput.ReadAsync(buffer, 0, buffer.Length)!;
                var output = outputRead > 0 ? new string(buffer, 0, outputRead) : null;
                Console.Write(output);
            }
            if (errorRead <= 0) continue;
            
            errorRead = await process?.StandardError.ReadAsync(buffer, 0, buffer.Length)!;
            var error = outputRead > 0 ? new string(buffer, 0, errorRead) : null;
            Console.Write(error);
        }
            
        await process?.WaitForExitAsync()!;
        Console.ReadKey();
    }
}

This pulls the steamcmd stdout in C#, you seem to get it in blocks but it works.

@Yetoo1
Copy link

Yetoo1 commented Jan 9, 2022

@LostBeard Did you see the solutions for at least the progress and other generic output with conpty/winpty/node-pty higher up in the thread.
@ScottKane But does it get steam guard prompt and other prompts?

@ScottKane
Copy link

@Yetoo1 it didn't. I'm now just executing python as a command proxy. I wonder if there is a way to run steamcmd unbuffered so that the fact they aren't flushing doesn't matter

@ScottKane
Copy link

ScottKane commented Jan 10, 2022

Ok so this works in C#

This is using https://github.com/microsoft/vs-pty.net. They don't publish the nuget packages so you can build it locally (which is a bit fiddly). I did the following:

make sure to also clone the sub modules

  1. I upped the sdk version in the global.json to current. (which I have installed)
  2. Deleted OSS.Sign and Pty.Net.Tests
  3. Edit src/Directory.Build.props - comment out GitVersioning package reference
  4. Edit Pty.Net.csproj - set target framework netstandard2.1 and comment out project reference to OSS.Sign
  5. (Optional) Remove Console.WriteLine calls from Pty.Net.Unix.PtyConnection.ChildWatcherThreadProc()
  6. Build

If you skip 5 you will have Waiting on {this.pid} prepended to your output stream and Wait succeeded or Wait failed with {errno} appended to the end.

internal static class Program
{
    public static async Task Main(string[] args)
    {
        var processExited = new TaskCompletionSource<uint>();
        try
        {
            var tokenSource = new CancellationTokenSource();
            var connection = await PtyProvider.SpawnAsync(new PtyOptions()
            {
                Cwd = Environment.CurrentDirectory,
                App = "/usr/bin/steamcmd",
                CommandLine = new[] {"+login anonymous", "+app_update 343050 validate", "+quit"}
            }, tokenSource.Token);

            connection.ProcessExited += (sender, e) => processExited.TrySetResult((uint)e.ExitCode);

            var buffer = new byte[1024];
            var output = string.Empty;
            var read = 1;
            
            while(!tokenSource.Token.IsCancellationRequested && !processExited.Task.IsCompleted && read > 0)
            {
                read = await connection.ReaderStream.ReadAsync(buffer.AsMemory(0, buffer.Length), tokenSource.Token);
                output += Encoding.UTF8.GetString(buffer, 0, read);
                await connection.ReaderStream.FlushAsync(tokenSource.Token);

                foreach (var line in output.Split('\n'))
                {
                    if (!string.IsNullOrWhiteSpace(line))
                        Console.WriteLine(line);

                    output = output[line.Length..].TrimStart('\r', '\n');
                }
            }
            
            // Code here will never run
        }
        catch (IOException e)
        {
            // Don't throw expected error
            if ((e.HResult & 0x0000FFFF) != 5 || processExited.Task.Result is not (0xC000013A or 1 or 0))
                throw; 
        }
        finally
        {
            // Continue execution here
            Console.ReadKey();
        }
    }
}

Output:

Redirecting stderr to '/root/.steam/logs/stderr.txt'
[  0%] Checking for available updates...
[----] Verifying installation...
[  0%] Downloading update...
[  0%] Checking for available updates...
[----] Download complete.
[----] Extracting package...
[----] Extracting package...
[----] Extracting package...
[----] Extracting package...
[----] Installing update...
[----] Installing update...
[----] Installing update...
[----] Installing update...
[----] Installing update...
[----] Installing update...
[----] Installing update...
[----] Installing update...
[----] Cleaning up...
[----] Update complete, launching Steamcmd...
Redirecting stderr to '/root/.steam/logs/stderr.txt'
[  0%] Checking for available updates...
[----] Verifying installation...
Steam Console Client (c) Valve Corporation - version 1639697740
-- type 'quit' to exit --
Loading Steam API...OK
Connecting anonymously to Steam Public...
OK
Waiting for client config...OK
Waiting for user info...
Warning: failed to init SDL thread priority manager: SDL not found
OK
 Update state (0x3) reconfiguring, progress: 0.00 (0 / 0)
 Update state (0x3) reconfiguring, progress: 0.00 (0 / 0)
 Update state (0x3) reconfiguring, progress: 0.00 (0 / 0)
 Update state (0x3) reconfiguring, progress: 0.00 (0 / 0)
 Update state (0x3) reconfiguring, progress: 0.00 (0 / 0)
 Update state (0x3) reconfiguring, progress: 0.00 (0 / 0)

@ScottKane
Copy link

Worth noting that it's also cross platform so should be fine on Unix/Linux/Mac and Windows.

@Vlad-00003
Copy link

Ok so this works in C#

This is using https://github.com/microsoft/vs-pty.net. They don't publish the nuget packages so you can build it locally (which is a bit fiddly). I did the following:

I know it's been a long while, but I got to ask - how did you managed to make it work?
I've spend 2 days already and feel like I'm banging my head against the wall - both Pty.Net and conpty compiled successfully, but it looks like something was changed down the line - pty.net keeps crushing on StartPseudoConsoleAsync with System.ArgumentException: "Value does not fall within the expected range."

pty.net built on x64, conpty build from the same submodule used in pty.net and I have no idea where I can go from here

@Vlad-00003
Copy link

dang. Figured it out.

in windows you HAVE TO set Cols & Rows (not idea about other os). So yeah. 2 variables actually fixes everything

@ScottKane, thank you so much for this solution!

@ScottKane
Copy link

ScottKane commented Sep 26, 2022

@Vlad-00003 I was actually building a wrapper CLI around SteamCMD called commander, I got side-tracked with other work but it works so far to allow you to install a game server etc, code can be found here: https://github.com/ScottKane/Commander

Just run publish (as ready to run single file) for whichever platform e.g win-x64 which will produce commander.exe under Commander\bin\Release\net6.0\win-x64\publish. Run the exe and take a look, there's a load of code in there sampling how to use the progress bar package I was looking at to replace the text spamming update process out of SteamCMD but that's as far as I got, there's also a dockerfile in there as this was intended to make installing server game files to a docker image easy but you can use the CLI as a replacement for SteamCMD.

Hope this helps :)

@Vlad-00003
Copy link

Vlad-00003 commented Sep 26, 2022

Replying to #1684 (comment)

And here you got with another save again! Thanks :)
I would definitely take a look

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests