Can I replace %USERPROFILE% and still get KNOWNFOLDERIDs from the registry? - windows

We're developing an open source Python library that runs on Linux, MacOS, and Windows, but we don't have much experience or exposure to Windows in the developer team. The way we setup and run our test suite works fine under Linux and Mac, but is suboptimal on Windows.
Our tests set up a new directory in a temporary location, place a fake .gitconfig with relevant configurations inside it, and have the relevant HOME environment variables point to this location as the home directory in order to pick up the configurations during testing.
The code is shortened and can't be run, but hopefully illustrates the gist of what we do:
with make_tempfile(mkdir=True) as new_home:
pass
for v, val in get_home_envvars(new_home).items():
set_envvar(v, val)
if not os.path.exists(new_home):
os.makedirs(new_home)
with open(os.path.join(new_home, '.gitconfig'), 'w') as f:
f.write("""\
[user]
name = Tester
email = test#example.com
[more configs for testing]
exc = 1
""")
where get_home_envvars() makes sure that the $HOME env variable points to the new, temporary test home. On Windows since Python 3.8, os.path no longer queried the $HOME variable to determine a user's home, but USERPROFILE[1 ][2], so we've just overwritten this variable with the temporary test home:
def get_home_envvars(new_home):
environ = os.environ
out = {'HOME': new_home}
if on_windows:
# requires special handling, since it has a number of relevant variables
# and also Python changed its behavior and started to respect USERPROFILE only
# since python 3.8: https://bugs.python.org/issue36264
out['USERPROFILE'] = new_home
out['HOMEDRIVE'], out['HOMEPATH'] = splitdrive(new_home)
return {v: val for v, val in out.items() if v in os.environ}
However, we have now discovered that this breaks our test setup on Windows, with tests "bleeding" their caches, cookie data bases etc. into the places where we perform our unit tests, and with this creating files and directories that break our test assumptions.
I have a very limited understanding on what happens exactly, but my current hypothesis is this: Our library determines the appropriate locations for caches, logs, cookies, etc upon start by using appdirs [3], which does so by querying the "special folder" IDs/ CSIDLs that Windows has [4]. This information is determined in the Windows registry - which is found based on the USERPROFILE. To quote one specific reply in the Python bug tracker to this change:
This is unfortunate. Modifying USERPROFILE is highly unusual. USERPROFILE is the location of the user's "NTUSER.DAT" registry hive and local application data ("AppData\Local"), including "UsrClass.dat" (the "Software\Classes" registry hive). It's also the default location for a user's known shell folders and home directory. Modifying USERPROFILE shouldn't cause problems with any of this, but I'm not completely at ease with it.
After our testsuite setup is done, we start new processes that run our tests. The new processes only get to see the new USERPROFILE, and appdirs returns the paths it finds by sending them through normpath, which unfortunately interprets the empty string returned by _get_win_folder for a CSIDL that now can't be found anymore as a relative path (.):
# snippet from appdirs source code
path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
And based on this, we end up configuring the current working directory of each test as the place for user data, user caches, etc.
My question is: How could I fix this? Based on my probably incomplete understanding, I currently think it ultimately boils down to the question how to treat or mock the USERPROFILE. I need to have it pointed to a registry in order to derive the "special folder" IDs (be it with appdirs or more modern replacements of it) - but I also need it to point to the fake home with test-specific Git configurations. I believe the latter requires overwriting USERPROFILE in Python3.8 and newer. I'm wondering if there is a way to copy or mock the registry and place it under the new home? Set relevant CSIDLs/KNOWNFOLDERIDs in some other way? Hardcode other temporary locations to use as cache directories etc? Or maybe there is a more clever way to run a test suite under Windows that does not require a fake home?
I would be very grateful to learn from more experienced Windows developers what to do, or also what not to do. Many thanks in advance.
[1] https://docs.python.org/3.11/library/os.path.html#os.path.expanduser
[2] https://bugs.python.org/issue36264
[3] https://github.com/ActiveState/appdirs
[4] https://learn.microsoft.com/en-us/windows/win32/shell/csidl

Related

Can Trains config file be specified dynamically or relative to the running script path?

Suppose I have a server where many users run different experiments, possibly with different Trains Servers.
I know about the TRAINS_CONFIG_FILE environment variable, but I wonder if this can be made more flexible in one of the following ways:
Specifying the Trains config file dynamically, i.e. during runtime of the training script?
Storing a config file in each of the training repos and specifying its path relatively to the running script path (instead of relatively to ~/)?
Disclaimer: I'm a member of Allegro Trains team
Loading of the configuration is done at import time. This means that if you set the os environment before importing the package, you should be fine:
os.environ['TRAINS_CONFIG_FILE']='~/repo/trains.conf'
from trains import Task
The configuration file is loaded based on the current working directory, this means that if you have os.environ['TRAINS_CONFIG_FILE']='trains.conf' the trains.conf file will be loaded from the running directory at the time the import happens (which usually is the folder where your script is executed from). This means you can have it as part of the repository, and always set the TRAINS_CONFIG_FILE to point to it.
A few notes:
What is the use case for different configuration files ?
Notice that when running with trains-agent , this method will override the configuration that the trains-agent passes to the code.

How to create a dynamic local file path that changes based on the windows user

I have created a local windows filepath that is dynamic based on the user.
C:\Users\%USERPROFILE%\rest_of_filepath
This path works perfectly on my local machine, but when I email it to someone else, they get this error:
We can't find 'C:\Users\%USERPROFILE%\rest_of_path'. Please make sure you are suing the correct location or web address.
How to I get it to work for the others I am sending it to?
The USERPROFILE is the the correct Environment Variable to use to retrieve the path of the Windows user profile folder. This will work even if the folder is in a "non-default" location. Windows created this Environment Variable for this exact reason. Encasing a word in % informs Windows that the word inside the % is an Environment Variable. Windows therefore knows to replace the entire '%USERPROFILE%' with the path associated to that variable.
In the case of the question the path C:\Users\%USERPROFILE%\rest_of_filepath is used. The C:\Users\ portion of this path is unnecessary and results in C:\Users\C:\Users\<userprofilefolder>\rest_of_path since the Environment Variable will also retrieve that portion of the path.
%USERPROFILE\rest_of_path is all that is needed.
Ultimately, even if the HTML interpreter in the email client was able to parse Environment Variables, access to local system files from email is not allowed for security reasons.
Emailing the path and instructing the recipient to copy and paste the it into the Windows file explorer is the recommended work-around.
src: Recognized Environment Variables that are recognized only in the user context

Good practice GCE + Windows: computer name

I have some Windows Server 2016 instances on GCE (for Jenkins agents).
I'm wondering what is the best/good practice when it comes to computer name.
Currently, when I want to create a new node, I clone an instance (create images from disks + create template + create instance from template).
On this clone, I change the computer name (in Windows) so that it has the same name as on GCE. Is it useful? recommended? bad? needed?
I know that the name of the Jenkins node needs to be the same as the name of the GCE instance (to be picked up easily). However, I don't think the Windows computer name matters.
So, should I pick an identical generic name for all of them? A prefix+random generated name? Continue with the instance=computer=node name?
The node name that I use in Jenkins is always retrieved from env.NODE_NAME (when needed), so that should not break any pipeline. Not sure thought, as I may be missing something (internal to Jenkins).
Bonus question: After cloning, I have to do some modifications on the clone for Perforce (p4) to work.
I temporarily set some env variables
I duplicate the workspace: p4 client -t prefix-buildX-suffix prefix-buildY-suffix
I setup the stream (not sure if doable in one step)
Then regenerate the list of files: p4 sync -k <root_folder_to_be_generated>/...#YYYY/MM/DD
So, here also there's a name prefix-buildY-suffix which is the same as the one from the instance=computer=node (buildY). It may be a separate question, but as it's still from the same context, I'm putting it here: should I recreate a new workspace all the time? Knowing that it's on several machines, I'd say yes. Otherwise, I "imagine" that p4 would have contradictory information about the state of this workspace. So, here also, I currently need to customize the name. So, even if I make the Windows computer name generic, I would still need to customize the p4 workspace name, wouldn't I?
Jenkins must have the same computer name as the one on the network.
So, all three names must be identical.

windows registry storage best practice

Background
I've recently been shunted into the world of windows programming and I'm still trying to find my way around the best practices and ways of doing things. So I was just hoping for some pointers on use of the registry
Not particularly relevant but the background is that I am creating an installer in Golang, a couple of points to get out the way on that:
I am aware MSI's would usually be best practice for an installer (I have my reasons for going custom exe)
I know there are more obvious language choices than golang, just go with it
Current registry use
As part of the install process, I store several pieces of data in the registry:
run once commands:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce
I create a few entries here: to restart the process after a system reboot and to delete some temp files on reboot after uninstall
an uninstall entry:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Vendor
Product
Content here is the same as an MSI would create, I was careful not to create any additional custom fields here (all static data until uninstall)
an application entry:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Vendor\Product
I store some additional data about the installation here, some of which is needed for uninstall such as state info from before installation (again all static content)
a temporary entry:
Computer\HKEY_CURRENT_USER\SOFTWARE\Vendor\Product
I store some temporary data here which can include some sensitive user entered data (usernames/passwords). I run some symmetric encryption to obscure the data though my understanding is this is area of the registry is encrypted so only the user could access anyway (would like confirmation on that)
This data is used to resume after restart and then deleted
Questions
I'm looking for confirmation / corrections on my current use of the registry?
I now have need to pass some data between an application and a running service, this data would be updated every 1-2 minutes and would be a few bytes of JSON. Does the registry seem like a reasonable place to store variable data like this? If so is there a particular place that better for variable data - I was going to add it to:
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Vendor\Product
HCKU isn't encrypted to my knowledge. It's stored in a file called NTUser.dat and could be loaded as a hive under HKEY_USERS and visible to other processes with sufficient rights to do so.
You would need to open up the rights to HKLM\SOFTWARE\Vendor\Product if you expect a user priv process to be able to write to it. If you want to pass data to a service you might want to use some sort of IPC pipe to do so. Not sure what's available in Golang for this.

How to make Passwd::Keyring::Auto persistent on Windows

I'm using Passwd::Keyring::Auto for Perl on Windows. I noticed that the keyring is not persistent.
Can I force it to be persistent on Windows?
http://search.cpan.org/~mekk/Passwd-Keyring-Auto-0.2703/lib/Passwd/Keyring/Auto.pm
Ex.
use Passwd::Keyring::Auto;
my $keyring = get_keyring(app=>"Test", group=>"Windows");
my $username = "someuser";
my $password = $keyring->set_password($username, $password, "mylostspace.com");
When my program ends, I'd like to get whatever passwords I had in the keyring like below:
$password = $keyring->get_password($username, "sometest.com");
However, the $keyring->is_persistent() always returns 0. I tried forcing the option PERSISTENT => 1 when I create the keyring, but that didn't work.
Thanks in advance
I simply haven't developed windows backend yet, as I do not own windows machine at the moment. Writing module like Passwd::Keyring::WindowsVault (or similar) should not be hard (especially considering one can consult python keyring library source for inspiration), but requires some programmer with Windows development environment. In case you are (or anybody else is) interested in writing one, I would be glad to help, but I am simply unable to test such a module or even to prepare binary distribution for CPAN.
Once such module exists, integrating it into Passwd::Keyring::Auto would be trivial
Pointers:
(what should Passwd::Keyring backend implement)
https://metacpan.org/pod/distribution/Passwd-Keyring-Auto/lib/Passwd/Keyring/Auto/KeyringAPI.pm
(APIs used by pythonic library)
https://bitbucket.org/kang/python-keyring-lib/src/8aadf61db38c70a5fe76fbe013df25fa62c03a8d/keyring/backends/Windows.py?at=default
(in perl it should be replaced with some XS as I do not know about anything like ctypes, module code structure would likely be similar to that of https://bitbucket.org/Mekk/perl-keyring-gnome/src )
And one more note: with some effort it should be possible to use Passwd::Keyring::PwSafe3 backend on Windows, to keep passwords persistent. You will still need to provide opening password for this storage (no open thanks to OS authorization) on every run but in case you have multiple passwords or want to manage them from GUI too it may make sense. You can try setting environment variable PASSWD_KEYRING_AUTO_PREFER to PwSafe3 to use this keyring (of course install the module beforehand).

Resources