Managed to get it working but when i do a sysprep and boot for the first time its asking me to create a new password on the VNC

I have this enabled.

inject_user_password=true
first_logon_behaviour=false

This is my code

Code:
[DEFAULT]
username=Administrator
groups=Administrators
netbios_host_name_compatibility=true
inject_user_password=true
first_logon_behaviour=false
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
locations=cdroom
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService
verbose=true
debug=true
enable_automatic_updates=true
logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
logfile=cloudbase-init.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
logging_serial_port_settings=COM1,115200,N,8
mtu_use_dhcp_config=true
ntp_use_dhcp_config=true
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
plugins=plugins = cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.windows.ntpclient.NTPClientPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.networkconfig.NetworkConfigPlugin, cloudbaseinit.plugins.windows.licensing.WindowsLicensingPlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.winrmlistener.ConfigWinRMListenerPlugin, cloudbaseinit.plugins.windows.winrmcertificateauth.ConfigWinRMCertificateAuthPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin
allow_reboot=false
stop_service_on_exit=false
check_latest_version=true

Can anyone help on this?
Ok It has taken me quite a few cycles to get back to this post, sorry for the late reply, I know the feeling of being stuck.

Here is my cloudbase-init.conf file working to the dot:

Code:
[DEFAULT]
username=Administrator
groups=Administrators
netbios_host_name_compatibility=true
inject_user_password=true
first_logon_behaviour=false
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
locations=cdroom
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService
verbose=true
debug=true
ntp_use_dhcp_config=true
real_time_clock_utc=true
ntp_enable_service=true
rdp_set_keepalive=true
enable_automatic_updates=true
logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
logfile=cloudbase-init.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
logging_serial_port_settings=COM1,115200,N,8
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
plugins=plugins = cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.windows.ntpclient.NTPClientPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.windows.createuser.CreateUserPlugin, cloudbaseinit.plugins.common.networkconfig.NetworkConfigPlugin, cloudbaseinit.plugins.windows.licensing.WindowsLicensingPlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin, cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.windows.winrmlistener.ConfigWinRMListenerPlugin, cloudbaseinit.plugins.windows.winrmcertificateauth.ConfigWinRMCertificateAuthPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin
allow_reboot=true
stop_service_on_exit=false
check_latest_version=true

And do not dare to forget to also change your cloudbase-init-unattend because that's the one that actually runs on first boot after sysprep

Code:
[DEFAULT]
username=Administrator
groups=Administrators
inject_user_password=false
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
verbose=true
debug=true
logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
logfile=cloudbase-init-unattend.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
logging_serial_port_settings=COM1,115200,N,8
mtu_use_dhcp_config=true
ntp_use_dhcp_config=true
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
check_latest_version=false
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService,cloudbaseinit.metadata.services.httpservice.HttpService,cloudbaseinit.metadata.services.ec2service.EC2Service,cloudbaseinit.metadata.services.maasservice.MaaSHttpService
plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin,cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin
allow_reboot=true
stop_service_on_exit=false

Copy/Paste, adjust your user, sysprep and enjoy
 
  • Like
Reactions: sebyp and cakeoats
Hi and many thanks for your post OP, great addition!

After I run sysprep on finished installation with Cloudbase-init installed, clone the VM and add a clouddrive to set PW, windows asks me to reset the password. Why does it this happen? I thought the PW I set in clouddrive should be the administrator password.....
I can confirm WITHOUT sysprep the clouddrive sets password and it works perfect... but then SID is the same!!

What am I missing? I'm using default Unattend.xml from Cloudbase. Please help, anyone


EDIT: (how to fix):
Sysprep by default reset the Administator password (or-so, it sets the password to an empty string forcing you to reset it...)
To avoid this, set "username=admin" in .conf, make sure the during the installation of Cloudbase-init you specify to use "admin" as the user.
Enable the plugin: "cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin"

Maybe disable the built-in administrator: net user administrator /active:no

EDIT DONE


REAL FIX IS: Change first_logon_behaviour=no to first_logon_behaviour=false on both config files.
---------
here is my cloudbase-init files:
cloudbase-init.conf
Code:
[DEFAULT]
username=Administrator
groups=Administrators
netbios_host_name_compatibility=true
inject_user_password=true
first_logon_behaviour=no
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
locations=cdrom
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService
verbose=true
debug=true
ntp_use_dhcp_config=true
real_time_clock_utc=true
ntp_enable_service=true
rdp_set_keepalive=true
enable_automatic_updates=true
log_dir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
log_file=cloudbase-init.log
logging_serial_port_settings=COM1,115200,N,8
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.windows.ntpclient.NTPClientPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.common.networkconfig.NetworkConfigPlugin, cloudbaseinit.plugins.windows.licensing.WindowsLicensingPlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin
allow_reboot=true
stop_service_on_exit=false
check_latest_version=true

cloudbase-init-unattend
Code:
[DEFAULT]
username=Administrator
groups=Administrators
inject_user_password=true
first_logon_behaviour=no
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
locations=cdrom
verbose=true
debug=true
log_dir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
log_file=cloudbase-init-unattend.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
logging_serial_port_settings=COM1,115200,N,8
mtu_use_dhcp_config=true
ntp_use_dhcp_config=true
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
check_latest_version=false
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService,cloudbaseinit.metadata.services.httpservice.HttpService,cloudbaseinit.metadata.services.ec2service.EC2Service,cloudbaseinit.metadata.services.maasservice.MaaSHttpService
plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin,cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin
allow_reboot=true
stop_service_on_exit=false
 
Last edited:
Hi and many thanks for your post OP, great addition!

After I run sysprep on finished installation with Cloudbase-init installed, clone the VM and add a clouddrive to set PW, windows asks me to reset the password. Why does it this happen? I thought the PW I set in clouddrive should be the administrator password.....
I can confirm WITHOUT sysprep the clouddrive sets password and it works perfect... but then SID is the same!!

What am I missing? I'm using default Unattend.xml from Cloudbase. Please help, anyone


EDIT: (how to fix):
Sysprep by default reset the Administator password (or-so, it sets the password to an empty string forcing you to reset it...)
To avoid this, set "username=admin" in .conf, make sure the during the installation of Cloudbase-init you specify to use "admin" as the user.
Enable the plugin: "cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin"

Maybe disable the built-in administrator: net user administrator /active:no

EDIT DONE
---------
here is my cloudbase-init files:
cloudbase-init.conf
Code:
[DEFAULT]
username=Administrator
groups=Administrators
netbios_host_name_compatibility=true
inject_user_password=true
first_logon_behaviour=no
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
locations=cdrom
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService
verbose=true
debug=true
ntp_use_dhcp_config=true
real_time_clock_utc=true
ntp_enable_service=true
rdp_set_keepalive=true
enable_automatic_updates=true
log_dir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
log_file=cloudbase-init.log
logging_serial_port_settings=COM1,115200,N,8
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin, cloudbaseinit.plugins.windows.ntpclient.NTPClientPlugin, cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.common.networkconfig.NetworkConfigPlugin, cloudbaseinit.plugins.windows.licensing.WindowsLicensingPlugin, cloudbaseinit.plugins.common.sshpublickeys.SetUserSSHPublicKeysPlugin, cloudbaseinit.plugins.common.userdata.UserDataPlugin, cloudbaseinit.plugins.common.setuserpassword.SetUserPasswordPlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin
allow_reboot=true
stop_service_on_exit=false
check_latest_version=true

cloudbase-init-unattend
Code:
[DEFAULT]
username=Administrator
groups=Administrators
inject_user_password=true
first_logon_behaviour=no
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
locations=cdrom
verbose=true
debug=true
log_dir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
log_file=cloudbase-init-unattend.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
logging_serial_port_settings=COM1,115200,N,8
mtu_use_dhcp_config=true
ntp_use_dhcp_config=true
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
check_latest_version=false
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService,cloudbaseinit.metadata.services.httpservice.HttpService,cloudbaseinit.metadata.services.ec2service.EC2Service,cloudbaseinit.metadata.services.maasservice.MaaSHttpService
plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin,cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin
allow_reboot=true
stop_service_on_exit=false
Change first_logon_behaviour=no to first_logon_behaviour=false on both config files.
 
  • Like
Reactions: cakeoats
Hello everyone, i edited the patch a bit and i successfully create a variable in the metadata json file.
I am trying to push cloudbase to get the username from the metadata. I've looked into the baseopenstackservice.py to see which variable it looks for in the metadata;

Python:
def get_admin_username(self):
    return self._get_meta_data().get('meta', {}).get('admin_username')

Well my patch successfully creates the json file with admin_username variable as intended with the information i gave him throught proxmox ui.

This part of the createuser.py file seems to get the admin_username or the username from the conf file;

Python:
def execute(self, service, shared_data):
        user_name = service.get_admin_username() or CONF.username

But cloudbase allways use the username from the conf file and when i tried to remove it, it used the default username which is admin. Cloudbase gets the password and all the other data from the same file so it doesnt seem like a access issue and i also dont have any errors on the logs while booting.

What am i missing?
btw i am gonna share the edited patch here with username changes(activates administrator user when you dont type anything with a litle python localscript, puts ssh keys because one in this patch does not provide the expected syntax in the json).
 
  • Like
Reactions: vanttech
Hello everyone, i edited the patch a bit and i successfully create a variable in the metadata json file.
I am trying to push cloudbase to get the username from the metadata. I've looked into the baseopenstackservice.py to see which variable it looks for in the metadata;

Python:
def get_admin_username(self):
    return self._get_meta_data().get('meta', {}).get('admin_username')

Well my patch successfully creates the json file with admin_username variable as intended with the information i gave him throught proxmox ui.

This part of the createuser.py file seems to get the admin_username or the username from the conf file;

Python:
def execute(self, service, shared_data):
        user_name = service.get_admin_username() or CONF.username

But cloudbase allways use the username from the conf file and when i tried to remove it, it used the default username which is admin. Cloudbase gets the password and all the other data from the same file so it doesnt seem like a access issue and i also dont have any errors on the logs while booting.

What am i missing?
btw i am gonna share the edited patch here with username changes(activates administrator user when you dont type anything with a litle python localscript, puts ssh keys because one in this patch does not provide the expected syntax in the json).

Could you also post the output of your COM serial console during boot, if you activated verbose on the cloudinit config on the Windows guest then maybe we can figure out why is it giving you the errors. I have thus far been able to use the process described here up-to windows server 2019 without issues, I'm currently trying to get some cycles free to go test Windows 11
 
  • Like
Reactions: cansoylu
Could you also post the output of your COM serial console during boot, if you activated verbose on the cloudinit config on the Windows guest then maybe we can figure out why is it giving you the errors. I have thus far been able to use the process described here up-to windows server 2019 without issues, I'm currently trying to get some cycles free to go test Windows 11
Okey so i was using the stable release, i looked in to service/openstack and didnt see the get_admin_username definition there, so i installed the continous (beta build). And it gives me this output on serial but doesnt create a user. pastebin serial link

Well i looked through the pastebin and it still uses the username from the conf file which is Administrateur;
Code:
2021-11-12 16:45:25.991 4488 INFO cloudbaseinit.plugins.common.createuser [-] Setting password for existing user "Administrateur"

I checked the git, somebody committed the get_admin_username on the openstack service in this link; commit link

The commit was done 10 months ago, i am starting to think that it is not in the last release(even the beta)..
 
Last edited:
Okey so i was using the stable release, i looked in to service/openstack and didnt see the get_admin_username definition there, so i installed the continous (beta build). And it gives me this output on serial but doesnt create a user. pastebin serial link

Well i looked through the pastebin and it still uses the username from the conf file which is Administrateur;
Code:
2021-11-12 16:45:25.991 4488 INFO cloudbaseinit.plugins.common.createuser [-] Setting password for existing user "Administrateur"

I checked the git, somebody committed the get_admin_username on the openstack service in this link; commit link

The commit was done 10 months ago, i am starting to think that it is not in the last release(even the beta)..
This is an odd one, according to your console output cloudbase is actually finding a user in your guest OS with that name but is not there? That sounds like they did not include that commit on the latest beta build just yet. I always use continuous beta for testing since the stable is always lagging behind. But it begs the question of how is that check coming back with an "existing user"
 
This is an odd one, according to your console output cloudbase is actually finding a user in your guest OS with that name but is not there? That sounds like they did not include that commit on the latest beta build just yet. I always use continuous beta for testing since the stable is always lagging behind. But it begs the question of how is that check coming back with an "existing user"
Well i have "Administrateur"(which is Administrator account in french windows), it is the default username that i have in the cloudbases conf file. I wanted to override it with my input from proxmox user in cloud init. My patch generates a json file with the correct variable which is "admin_username" but cloudbase doesnt get the username from metadata in the cd drive. The functionality got added 10 months ago and the last official release dates to 1 year so i think that i need to compile the cloudbase init from git but i hove no idea how.

Edit: Well this was because the admin username def searches for the username inside a dictionary called meta in the metadata file. Since other defs of the openstack service work with bothways ( directly in the file or from meta dict from the same file) it was working but this wasnt.
 
Last edited:
Well i have "Administrateur"(which is Administrator account in french windows), it is the default username that i have in the cloudbases conf file. I wanted to override it with my input from proxmox user in cloud init. My patch generates a json file with the correct variable which is "admin_username" but cloudbase doesnt get the username from metadata in the cd drive. The functionality got added 10 months ago and the last official release dates to 1 year so i think that i need to compile the cloudbase init from git but i hove no idea how.

Edit: Well this was because the admin username def searches for the username inside a dictionary called meta in the metadata file. Since other defs of the openstack service work with bothways ( directly in the file or from meta dict from the same file) it was working but this wasnt.
Thinks,I referred to the git link you provided and modified the relevant code.
I just solved the problem you mentioned, and I made changes in these places:
cloudbaseinit/metadata/services/baseopenstackservice.py
Before modification
Python:
    def get_admin_username(self):
        return self._get_meta_data().get('meta', {}).get('admin_username')
After modification
Python:
     def get_admin_username(self):
         meta_data = self._get_meta_data()
         meta = meta_data.get('meta')

         if meta and'admin_username' in meta:
             adminname = meta['admin_username']
         elif'admin_username' in meta_data:
             adminname = meta_data['admin_username']
         else:
             adminname = None

         return adminname


Modify line 216 of the following file on the proxmox server
/usr/share/perl5/PVE/QemuServer/Cloudinit.pm

Before modification
Code:
sub configdrive2_metadata {
    my ($uuid) = @_;
    return <<"EOF";
{
     "uuid": "$uuid",
     "network_config": { "content_path": "/content/0000" }
}
EOF
}
After modification
Code:
sub configdrive2_metadata {
        my ($conf, $vmid, $user, $network) = @_;
        my $uuid = Digest::SHA::sha1_hex($user.$network);
        my $password = $conf->{cipassword};
        my $username = $conf->{ciuser};
        my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);
        my $startConfig =  <<"EOF";
{   
    "hostname": "$hostname",
    "uuid": "$uuid",
    "admin_pass": "$password",
    "admin_username": "$username",
EOF     
        if (defined(my $keys = $conf->{sshkeys})) {
            $startConfig .= "     \"network_config\": { \"content_path\": \"/content/0000\" },\n";
            $keys = URI::Escape::uri_unescape($keys);
            $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)];
            $keys = [grep { /\S/ } @$keys]; 
            $startConfig .= "     \"keys\": [\n";
            $startConfig .= "         {\n";
            
            my $keyCount = @$keys;
            for (my $i=0; $i < $keyCount; $i++) {
            #    $startConfig .= "  $keyCount   "
                if ($i == $keyCount-1){         
                    $startConfig .= "           \"key-$i\": \"".$keys->[$i]."\"\n";
                } else {
                    $startConfig .= "           \"key-$i\": \"".$keys->[$i]."\",\n";
                }
            }
            
            $startConfig .= "         }\n";
            $startConfig .= "     ]\n";

        } else{
            $startConfig .= "     \"network_config\": { \"content_path\": \"/content/0000\" }\n";
        }
        $startConfig.= "}";
        return $startConfig;
}

Modify at line 264
Before modification
Code:
if (!defined($meta_data)) {
    $meta_data = configdrive2_gen_metadata($user_data, $network_data);
    }
After modification
Code:
if (!defined($meta_data)) {
    $meta_data = configdrive2_metadata($conf, $vmid, $user_data, $network_data);
    }

After the modification is completed, restart the pvedaemon service
systemctl restart pvedaemon.service


If there is a problem that the custom password cannot be recognized, please refer to the following:
Because proxmox uses ciphertext to save the password of cloud-init by default, the windows template cannot be recognized, so you need to modify the code to save the password in plaintext
Modify /usr/share/perl5/PVE/API2/Qemu.pm

Line 1022 of the original code All passwords are encrypted
Code:
if (defined(my $cipassword = $param->{cipassword})) {
    # Same logic as in cloud-init (but with the regex fixed...)
    $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
        if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
    }

Replace with the new code, and save it in plaintext if it is a windows password
Code:
my $conf = PVE::QemuConfig->load_config($vmid);
my $ostype = $conf->{ostype};
if (defined(my $cipassword = $param->{cipassword})) {
    # Same logic as in cloud-init (but with the regex fixed...)
    if (!(PVE::QemuServer::windows_version($ostype))) {
        $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
            if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
    }
}
 
Last edited:
Thinks,I referred to the git link you provided and modified the relevant code.
I just solved the problem you mentioned, and I made changes in these places:
cloudbaseinit/metadata/services/baseopenstackservice.py
Before modification
Python:
    def get_admin_username(self):
        return self._get_meta_data().get('meta', {}).get('admin_username')
After modification
Python:
     def get_admin_username(self):
         meta_data = self._get_meta_data()
         meta = meta_data.get('meta')

         if meta and'admin_username' in meta:
             adminname = meta['admin_username']
         elif'admin_username' in meta_data:
             adminname = meta_data['admin_username']
         else:
             adminname = None

         return adminname


Modify line 216 of the following file on the proxmox server
/usr/share/perl5/PVE/QemuServer/Cloudinit.pm

Before modification
Code:
sub configdrive2_metadata {
    my ($uuid) = @_;
    return <<"EOF";
{
     "uuid": "$uuid",
     "network_config": { "content_path": "/content/0000" }
}
EOF
}
After modification
Code:
sub configdrive2_metadata {
        my ($conf, $vmid, $user, $network) = @_;
        my $uuid = Digest::SHA::sha1_hex($user.$network);
        my $password = $conf->{cipassword};
        my $username = $conf->{ciuser};
        my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);
        my $startConfig =  <<"EOF";
{  
    "hostname": "$hostname",
    "uuid": "$uuid",
    "admin_pass": "$password",
    "admin_username": "$username",
EOF    
        if (defined(my $keys = $conf->{sshkeys})) {
            $startConfig .= "     \"network_config\": { \"content_path\": \"/content/0000\" },\n";
            $keys = URI::Escape::uri_unescape($keys);
            $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)];
            $keys = [grep { /\S/ } @$keys];
            $startConfig .= "     \"keys\": [\n";
            $startConfig .= "         {\n";
           
            my $keyCount = @$keys;
            for (my $i=0; $i < $keyCount; $i++) {
            #    $startConfig .= "  $keyCount   "
                if ($i == $keyCount-1){        
                    $startConfig .= "           \"key-$i\": \"".$keys->[$i]."\"\n";
                } else {
                    $startConfig .= "           \"key-$i\": \"".$keys->[$i]."\",\n";
                }
            }
           
            $startConfig .= "         }\n";
            $startConfig .= "     ]\n";

        } else{
            $startConfig .= "     \"network_config\": { \"content_path\": \"/content/0000\" }\n";
        }
        $startConfig.= "}";
        return $startConfig;
}

Modify at line 264
Before modification
Code:
if (!defined($meta_data)) {
    $meta_data = configdrive2_gen_metadata($user_data, $network_data);
    }
After modification
Code:
if (!defined($meta_data)) {
    $meta_data = configdrive2_metadata($conf, $vmid, $user_data, $network_data);
    }

After the modification is completed, restart the pvedaemon service
systemctl restart pvedaemon.service


If there is a problem that the custom password cannot be recognized, please refer to the following:
Because proxmox uses ciphertext to save the password of cloud-init by default, the windows template cannot be recognized, so you need to modify the code to save the password in plaintext
Modify /usr/share/perl5/PVE/API2/Qemu.pm

Line 1022 of the original code All passwords are encrypted
Code:
if (defined(my $cipassword = $param->{cipassword})) {
    # Same logic as in cloud-init (but with the regex fixed...)
    $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
        if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
    }

Replace with the new code, and save it in plaintext if it is a windows password
Code:
my $conf = PVE::QemuConfig->load_config($vmid);
my $ostype = $conf->{ostype};
if (defined(my $cipassword = $param->{cipassword})) {
    # Same logic as in cloud-init (but with the regex fixed...)
    if (!(PVE::QemuServer::windows_version($ostype))) {
        $param->{cipassword} = PVE::Tools::encrypt_pw($cipassword)
            if $cipassword !~ /^\$(?:[156]|2[ay])(\$.+){2}/;
    }
}
Thanks anyway but i just changed the proxmox patch to generate all the variables inside the meta dictionary in that file and that solved the issu. Now all of that seems to work well, i will try the ssh key injection and network config tomorrow.
 
Thanks anyway but i just changed the proxmox patch to generate all the variables inside the meta dictionary in that file and that solved the issu. Now all of that seems to work well, i will try the ssh key injection and network config tomorrow.
Please tell me what is that proxmox patch,thanks!
 
Here it is, it activates the Administrator account if i dont enter user in cloudinit and it creates another account if i give in this case.
Code:
223    sub configdrive2_metadata {
224         my ($conf, $vmid, $user, $network) = @_;
225         my $uuid = Digest::SHA::sha1_hex($user.$network);
226         my $password = $conf->{cipassword};
227         my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);
228         my $username = undef;
229         if (defined($username = $conf->{ciuser})) {
230         } else{
231             $username = "Administrateur";
232         }
233         my $startConfig =  <<"EOF";
234 {
235     "meta": {
236         "hostname": "$hostname",
237         "uuid": "$uuid",
238         "admin_username": "$username",
239         "admin_pass": "$password"
240 EOF
241         $startConfig .= <<"EOF";
242
243
244         }
245 }
246 EOF
247         return $startConfig;
248
249 }

I will start working on the ssh keys and network config tomorrow, will share it in the forum when i finish.
 
Last edited:
  • Like
Reactions: vanttech
Here it is, it activates the Administrator account if i dont enter user in cloudinit and it creates another account if i give in this case.
Code:
223    sub configdrive2_metadata {
224         my ($conf, $vmid, $user, $network) = @_;
225         my $uuid = Digest::SHA::sha1_hex($user.$network);
226         my $password = $conf->{cipassword};
227         my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);
228         my $username = undef;
229         if (defined($username = $conf->{ciuser})) {
230         } else{
231             $username = "Administrateur";
232         }
233         my $startConfig =  <<"EOF";
234 {
235     "meta": {
236         "hostname": "$hostname",
237         "uuid": "$uuid",
238         "admin_username": "$username",
239         "admin_pass": "$password"
240 EOF
241         $startConfig .= <<"EOF";
242
243
244         }
245 }
246 EOF
247         return $startConfig;
248
249 }

I will start working on the ssh keys and network config tomorrow, will share it in the forum when i finish.

This looks good I will test it on my lab tonight or tomorrow but looks like it should work on PVE7, I will try W11 with the continuous dev version of cloudbase-init and report back.

EDIT:
I tested this with PVE 7.0-13 and Windows 11 Pro/Pro for Workstations. Happy to report it all worked like a charm with no issues I could find. In case anyone can't find where to modify the baseopenstackservice.py file in an already deployed install of Cloudbase-init this is the path
C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Lib\site-packages\cloudbaseinit\metadata\services

EDIT#2:

PVE7.1 is out and this still works well, just don't try and take the shortcut by replacing files, you will have to manually make the modifications once the upgrade to 7.1 is complete on your host.

If anyone knows how to tag Proxmox Staff on this thread it would be great. With how long we've been updating this post I would think the Proxmox staff should've already included these modifications in their releases, after all the code they've been shipping doesn't work.
 
Last edited:
  • Like
Reactions: Bruno Garcia
Greetings all! Been working on some automation with Terraform/Saltstack on Proxmox VE and found this thread, which has been of help, yet was not getting me across the finish line.

to start, I'm not trying to put the password in the configdrive right now and am using the unattend.xml for the administrator password. Reason being is using Terraform/SaltStack to change the password once the VM is up. For Terraform to work as needed, I required the static network to function as DHCP is not available in the current design. I've updated the cloudinit.pm to put the DNS and search on the first ethernet of a windows setup.
Perl:
sub configdrive2_network {
    my ($conf) = @_;

    ## Support Windows Cloud-init
    my $ostype = $conf->{"ostype"};
    my $defalt_dns = '';
    my $default_search = '';
    ##

    my $content = "auto lo\n";
    $content .= "iface lo inet loopback\n\n";

    my ($searchdomains, $nameservers) = get_dns_conf($conf);
    if ($nameservers && @$nameservers) {
        $nameservers = join(' ', @$nameservers);
        $content .= "        dns_nameservers $nameservers\n";
        $defalt_dns = $nameservers; # Support Windows Cloud-init
    }
    if ($searchdomains && @$searchdomains) {
        $searchdomains = join(' ', @$searchdomains);
        $content .= "        dns_search $searchdomains\n";
        $default_search = $searchdomains; # Support Windows Cloud-init
    }

    my @ifaces = grep { /^net(\d+)$/ } keys %$conf;
    foreach my $iface (sort @ifaces) {
        (my $id = $iface) =~ s/^net//;
        next if !$conf->{"ipconfig$id"};
        my $net = PVE::QemuServer::parse_ipconfig($conf->{"ipconfig$id"});
        $id = "eth$id";

        $content .="auto $id\n";
        if ($net->{ip}) {
            if ($net->{ip} eq 'dhcp') {
                $content .= "iface $id inet dhcp\n";
            } else {
                my ($addr, $mask) = split_ip4($net->{ip});
                $content .= "iface $id inet static\n";
                $content .= "        address $addr\n";
                $content .= "        netmask $mask\n";
                $content .= "        gateway $net->{gw}\n" if $net->{gw};

                ## Supporting Cloudbase-init using cloudrive plugin.
                ## This provides the DNS to the first Ethernet "eth0"
                if(PVE::QemuServer::windows_version($ostype) && ($id eq "eth0")) {
                    $content .= "        dns-nameservers $defalt_dns\n";
                    $content .= "        dns-search $default_search\n";
                }
                ####
            }
        }
        if ($net->{ip6}) {
            if ($net->{ip6} =~ /^(auto|dhcp)$/) {
                $content .= "iface $id inet6 $1\n";
            } else {
                my ($addr, $mask) = split('/', $net->{ip6});
                $content .= "iface $id inet6 static\n";
                $content .= "        address $addr\n";
                $content .= "        netmask $mask\n";
                $content .= "        gateway $net->{gw6}\n" if $net->{gw6};
            }
        }
    }

    return $content;
}

This works well with the Win Server 2022 build I been working on and does not affect Llinux, keeping the original operations.

I've also cleaned up my cloudbase-init configs. Lastly, looking at the SSH part I think I might know how to get it working. From what I can see in the Cloudbase-init code, it's looking for a list of Keys under "public-keys" in metadata. I have not setup an SSH server on a windows system to work with this but is interesting as I do deal with Linux a lot and having windows be the same would be a trip. So, going to enable the SSH server on my 2022 build and see if I can add the keys, while I'm messing with this template.

NOTE: I am keeping all my notes on this at Github. This is a public repo and is still a work in progress. I mainly created it for me to automate the updates on the 5 VE nodes. Already readded the hostname to metadata but have not pushed it up yet. Plus, I'm using Proxmox's methods for the code update, instead of making a new method, which is what is done in this thread. Will update as I add more functionality.
 
Last edited:
Ok tried the SSH keys without any modification. Am seeing cloudbase is looking at metadata for the keys in the logs. Am working on adding the "public-keys" to the metadata as a list and see it if bites on that.
 
Well, got the SSH keys working, but there a catch.

We seem to have chicken/egg issues with the keys and the profile.
Code:
2021-12-17 18:30:45.194 3000 INFO cloudbaseinit.init [-] Executing plugin 'SetUserSSHPublicKeysPlugin'
2021-12-17 18:30:45.194 3000 DEBUG cloudbaseinit.metadata.services.base [-] Using cached copy of metadata: 'openstack/latest/meta_data.json' _get_cache_data C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\metadata\services\base.py:74
2021-12-17 18:30:45.210 3000 DEBUG cloudbaseinit.metadata.services.base [-] Using cached copy of metadata: 'openstack/latest/meta_data.json' _get_cache_data C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\metadata\services\base.py:74
2021-12-17 18:30:45.225 3000 DEBUG cloudbaseinit.utils.classloader [-] Loading class 'cloudbaseinit.osutils.windows.WindowsUtils' load_class C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\utils\classloader.py:27
2021-12-17 18:30:45.225 3000 DEBUG cloudbaseinit.plugins.common.sshpublickeys [-] User home: C:\Users\Administrator execute C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\plugins\common\sshpublickeys.py:45
2021-12-17 18:30:45.240 3000 INFO cloudbaseinit.plugins.common.sshpublickeys [-] Writing SSH public keys in: C:\Users\Administrator\.ssh\authorized_keys
2021-12-17 18:30:45.256 3000 DEBUG cloudbaseinit.metadata.services.baseconfigdrive [-] Deleting metadata folder: 'C:\\Windows\\TEMP\\tmpq6sa1nu5' cleanup C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\metadata\services\baseconfigdrive.py:91
2021-12-17 18:30:45.272 3000 INFO cloudbaseinit.init [-] Plugins execution done

So, cloudbase-init now sees the SSH keys in the metadata of the cloudconfig, and states it wrote the data into C:\Users\Administrator\.ssh\authorized_keys, but it did not. there was no ".ssh" under the administrator profile. I even added a .ssh, cleared the cloud-init state in the registry and did the sysprep again, with the same result. cloud-init states it wrote the file, but the directory, let alone the file, was not there. I only removed the SSHplugin state from the registry and rebooted the VM (no sysprep). Low and behold, there was the authorized_keys, with the keys in it.

Thinking the profile isn't ready with the SSHKeys plugin fires and so the keys do not get added, even if cloud-init says the operation is complete. Which mean, we either need to add a local script to check on the profile, clear the state in the registry, and reboot, or something else.

this is what I did in the cloudinit.pm

Perl:
sub configdrive2_gen_metadata {
    my ($conf, $vmid, $user, $network) = @_;

    my $uuid_str = Digest::SHA::sha1_hex($user.$network);
    my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);

    ### Setting Public SSH Keys ###
    my $pubKey = "";
    if (defined(my $keys = $conf->{sshkeys})) {
        $pubKey .= "\n    \"public_keys\": {\n";
        $keys = URI::Escape::uri_unescape($keys);
        $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)];
        $keys = [grep { /\S/ } @$keys];

        my $keyCount = @$keys;
        for (my $i=0; $i < $keyCount; $i++) {
            if ($i == $keyCount-1){
                $pubKey .= "        \"$i\":\"".$keys->[$i]."\"\n";
            } else {
                $pubKey .= "        \"$i\":\"".$keys->[$i]."\",\n";
            }
        }
        $pubKey .= "    },";
    }

    return configdrive2_metadata($pubKey, $hostname, $uuid_str);
}

sub configdrive2_metadata {
    my ($pubKey, $hostname, $uuid) = @_;
    return <<"EOF";
{$pubKey
    "hostname": "$hostname",
    "uuid": "$uuid",
    "network_config": { "content_path": "/content/0000" }
}
EOF
}

Will be uploading the changes to my github repo in just a little. The Proxmox side of this looks good. the issues with the SSHkeys is ordering from Cloudbase-init. Might mess with that, but in my case, I would just add the keys with Saltstack or Terraform when the password is updated.

I put this in over the SSH keys issue found: https://github.com/cloudbase/cloudbase-init/issues/85
Repo is updated: https://github.com/makton-dev/Cloudbase-PVE-Mods
 
Last edited:
Well, got the SSH keys working, but there a catch.

We seem to have chicken/egg issues with the keys and the profile.
Code:
2021-12-17 18:30:45.194 3000 INFO cloudbaseinit.init [-] Executing plugin 'SetUserSSHPublicKeysPlugin'
2021-12-17 18:30:45.194 3000 DEBUG cloudbaseinit.metadata.services.base [-] Using cached copy of metadata: 'openstack/latest/meta_data.json' _get_cache_data C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\metadata\services\base.py:74
2021-12-17 18:30:45.210 3000 DEBUG cloudbaseinit.metadata.services.base [-] Using cached copy of metadata: 'openstack/latest/meta_data.json' _get_cache_data C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\metadata\services\base.py:74
2021-12-17 18:30:45.225 3000 DEBUG cloudbaseinit.utils.classloader [-] Loading class 'cloudbaseinit.osutils.windows.WindowsUtils' load_class C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\utils\classloader.py:27
2021-12-17 18:30:45.225 3000 DEBUG cloudbaseinit.plugins.common.sshpublickeys [-] User home: C:\Users\Administrator execute C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\plugins\common\sshpublickeys.py:45
2021-12-17 18:30:45.240 3000 INFO cloudbaseinit.plugins.common.sshpublickeys [-] Writing SSH public keys in: C:\Users\Administrator\.ssh\authorized_keys
2021-12-17 18:30:45.256 3000 DEBUG cloudbaseinit.metadata.services.baseconfigdrive [-] Deleting metadata folder: 'C:\\Windows\\TEMP\\tmpq6sa1nu5' cleanup C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\lib\site-packages\cloudbaseinit\metadata\services\baseconfigdrive.py:91
2021-12-17 18:30:45.272 3000 INFO cloudbaseinit.init [-] Plugins execution done

So, cloudbase-init now sees the SSH keys in the metadata of the cloudconfig, and states it wrote the data into C:\Users\Administrator\.ssh\authorized_keys, but it did not. there was no ".ssh" under the administrator profile. I even added a .ssh, cleared the cloud-init state in the registry and did the sysprep again, with the same result. cloud-init states it wrote the file, but the directory, let alone the file, was not there. I only removed the SSHplugin state from the registry and rebooted the VM (no sysprep). Low and behold, there was the authorized_keys, with the keys in it.

Thinking the profile isn't ready with the SSHKeys plugin fires and so the keys do not get added, even if cloud-init says the operation is complete. Which mean, we either need to add a local script to check on the profile, clear the state in the registry, and reboot, or something else.

this is what I did in the cloudinit.pm

Perl:
sub configdrive2_gen_metadata {
    my ($conf, $vmid, $user, $network) = @_;

    my $uuid_str = Digest::SHA::sha1_hex($user.$network);
    my ($hostname, $fqdn) = get_hostname_fqdn($conf, $vmid);

    ### Setting Public SSH Keys ###
    my $pubKey = "";
    if (defined(my $keys = $conf->{sshkeys})) {
        $pubKey .= "\n    \"public_keys\": {\n";
        $keys = URI::Escape::uri_unescape($keys);
        $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)];
        $keys = [grep { /\S/ } @$keys];

        my $keyCount = @$keys;
        for (my $i=0; $i < $keyCount; $i++) {
            if ($i == $keyCount-1){
                $pubKey .= "        \"$i\":\"".$keys->[$i]."\"\n";
            } else {
                $pubKey .= "        \"$i\":\"".$keys->[$i]."\",\n";
            }
        }
        $pubKey .= "    },";
    }

    return configdrive2_metadata($pubKey, $hostname, $uuid_str);
}

sub configdrive2_metadata {
    my ($pubKey, $hostname, $uuid) = @_;
    return <<"EOF";
{$pubKey
    "hostname": "$hostname",
    "uuid": "$uuid",
    "network_config": { "content_path": "/content/0000" }
}
EOF
}

Will be uploading the changes to my github repo in just a little. The Proxmox side of this looks good. the issues with the SSHkeys is ordering from Cloudbase-init. Might mess with that, but in my case, I would just add the keys with Saltstack or Terraform when the password is updated.

I put this in over the SSH keys issue found: https://github.com/cloudbase/cloudbase-init/issues/85
Repo is updated: https://github.com/makton-dev/Cloudbase-PVE-Mods


I'm not even sure at this point when/how but one of those broke the network settings being applied to the VMs. None of my W11 or W10 VMs are able to get the Network Settings applied, Username and Password still works fine though. Anyone else having this issue? I'm temporarily using qm guest exec in a script to setup the network but I was hoping someone could come up with an answer I might be missing.
 
I'm not even sure at this point when/how but one of those broke the network settings being applied to the VMs. None of my W11 or W10 VMs are able to get the Network Settings applied, Username and Password still works fine though. Anyone else having this issue? I'm temporarily using qm guest exec in a script to setup the network but I was hoping someone could come up with an answer I might be missing.
We just shared the whole project that we finished. You guys can follow the wiki and download the files from our git. Everything works on pve6 and 7.
I've rewritten the patch for a cleaner look and added a few fonctionalities and 3 scripts for some missing functionality on cloudbase-init.
You can check it here;
https://forum.proxmox.com/threads/h...init-for-your-windows-based-instances.103375/
 
Anybody has this working with Windows 2022/11?

I can't get it to work, network settings work fine but the password is always wrong. I did the proxmox edits from the first post.

Thanks!
 

About

The Proxmox community has been around for many years and offers help and support for Proxmox VE, Proxmox Backup Server, and Proxmox Mail Gateway.
We think our community is one of the best thanks to people like you!

Get your subscription!

The Proxmox team works very hard to make sure you are running the best software and getting stable updates and security enhancements, as well as quick enterprise support. Tens of thousands of happy customers have a Proxmox subscription. Get yours easily in our online shop.

Buy now!