If APIs are made available for things, I'd much prefer that route and will replace using Stored Procs as I can.
As pgutil.exe
matures I see this module slowly becoming a Crescendo wrapper around it.
Welcome to the Inedo Forums! Check out the Forums Guide for help getting started.
If you are experiencing any issues with the forum software, please visit the Contact Form on our website and let us know!
If APIs are made available for things, I'd much prefer that route and will replace using Stored Procs as I can.
As pgutil.exe
matures I see this module slowly becoming a Crescendo wrapper around it.
Me too! I couldn't not hear it every time I talked about it, so it had to change. I stumbled across that pronunciation documentation and the lightbulb went off
I've published the initial release of my module for managing Inedo ProGet to the PowerShell gallery! Pagootle (formerly InedoOps) allows you to manage your ProGet server via PowerShell.
You can via the source code for Pagootle at https://github.com/steviecoaster/Pagootle, read the documentation at https://steviecoaster.github.io/Pagootle/, or via the release on the PowerShell gallery at https://www.powershellgallery.com/packages?q=pagootle
If you're wondering Why the name Pagootle? Well it's a play on the pronunciation of their command-line executable: pgutil.exe! See: https://docs.inedo.com/docs/proget/api/pgutil#how-is-pgutil-pronounced
In the latest version of ProGet, when creating a Chocolatey Feed via the web interface, both v2 and v3 apis are enabled by default.
However, when creating a feed via pgutil
, only the v2 endpoint is enabled. For consistency, can we have v3 endpoints be enabled for Chocolatey feeds the same way they are in the web interface?
Late reply (was out at a conference), but this was in fact permissions related. I've added a check to ensure the Inedo service account has been granted modify permissions on the drop path upon creation, and it is working appropriately!
According to https://forums.inedo.com/topic/1038/drop-path-contents/4?_=1747409857938 the Drop Path folder should remove a package once it has been successfully imported into a feed.
I've configured a drop path for a chocolatey feed and the folder does not seem to be clearing of nupkg files once they are ProGet.
See:
ProGet version 2024.35 (Build 2)
Hello. Me....again.
As the title suggests, can one programmatically assign a license to ProGet itself, or must it be done through the WebUI as shown here:?:
I had a quick look through the stored procedures to see if anything jumped out, but it all seemed related to licenses tied to assets being stored in ProGet feeds, not the actual server license itself.
I'm away to play with DevTools to see if the browser points in the right direction, but figured I'd ask here as well as you'll provide a much better answer :)
Ah, that makes more sense! Yeah, Github is weird like that. I think if I provided the raw link then that right-click would have worked. Sorry for the confusion!
v20250407 doesn't follow any sort of semver convention that I am aware of, that looks more like a git tag (but even those usually follow at least semver 2? I'd expect this to be 2025.04.07. Perhaps they published this malformed upstream, and ProGet can't handle it?
Just a hunch :)
Thanks @dean-houston! That's good enough for me. Looking forward to trying the new migrator once it lands, sounds really really good!
Hi friends!
Firstly, I gotta say that that package migration utility you've built into Chocolatey feeds in ProGet is awesome.
That said, I'm looking to extend it. We're doing a webinar together in a few weeks and I'm looking to build some automation that not only migrates the packages, but builds the feeds as well.
I can trivially do this with PowerShell and choco.exe, but the overhead of using choco slows the process down considerably. It would just absolutely wonderful if you could provide the secret sauce that you use to get the list of packages from the source repo before you start pulling them down into the destination.
I imagine you are skipping using choco or nuget but rather using the nuget api directly, but I can't quite grep which endpoints are in play here. I can get a single package easily.
But....I need to get every package. I can craft urls once I have a list of packages that need migrated. I just can't.....get that. Help?
We can take this offline if you don't want/can't reveal the secret sauce here :)
It just occured to me that you probably only need the Set-CertPermission function. If you're just renewing a cert, everything else should be fine in your config.
You can call this on its own as well: https://github.com/steviecoaster/InedoOps/blob/main/source/private/Set-CertPermissions.ps1
The issue on windows is that the Service user running the Inedo web service doesn't have permissions to the private key on WIndows. The script can run 100% offline standalone. There are 0 references to the internet inside of it. The only way you would need the internet to use it is if you use Install-Module to install the entire InedoOps module, which you do not need to do. You could simply copy the function, and run it.
It is obviously good practice to read any code you find online before you blindly run it, but there are 0 references to the internet. Unless you are referring to the -Urls parameter. That is so you can control what port your ProGet web interface is bound too with your SSL certificate, not the internet in general.
It seems like you are dancing around the fact that this is permissions issue on the private key. This is a windows host, correct? and you are attempting to use a certificate that is inside an Windows Certificate store?
If so I urge you to try the code I linked, instead of fighting it :)
Awesome! Thanks for the quick response on this one!
Since Chocolatey CLI 2.0.0 we have added NuGet v3 support. As all the other repository vendors have deprecated NuGet v2 apis, the majority of our customers on other platforms have migrated their configuration to v3 endpoints.
It is also worth mentioning that other repository vendors don't provide an option, both v2 and v3 are just available without needing to manage configuration.
Hey @udi-moshe_0021,
My InedoOps PowerShell module has a Set-ProGetSSLConfig function that helps setups ProGet to server up content via HTTPS.
The nice thing about this particular function is that, since it doesn't interact with ProGet's web interface, but rather configuration, it can be ran standalone.
You can find the function here: https://github.com/steviecoaster/InedoOps/blob/main/source/public/Utility/Set-ProGetSSLConfig.ps1
The first example is likely the one you'll want to follow, but pay attention to the URL parameter and adjust it as needed as you'll likely want to use https://*:443/
for the value (or whatever port you typically serve SSL traffic over to ProGet).
As mentioned by @rhessinger this is likely a permissions issue on the private key, which the above handles for you.
Hope that helps!
@jipianu_mihnea_1277 said in Creating Users with Native API:
ant to use the Native API to create users and groups, but I do not know how to generate the password because I am unaware of th
Thanks for the shout-out @atripp! Yes, you can do Set-ProGetUserPassword -Credential (Get-Credential)
and supply the username of the account you with to set the password, as well as the new password.
The New-ProGetUser
function will do this internally for you when you pass in the credential object that function expects.
If you run into trouble, please do file an issue so I can try to fix it!
Thanks! I've gone ahead and made it public at https://github.com/steviecoaster/InedoOps. Still have a while before I put it on the Gallery, but I like developing in the open when I can!
I'm laboring on a new PowerShell module I'm tentatively calling InedoOps
, which enables an administrator to administer an Inedo ProGet instance via PowerShell. It is shaping up quite nicely, though I've got quite a bit more to do before I would consider it "ready".
Would there be objections to my publishing this to the PowerShell Gallery once I'm done? Right now the code is private on my Github. If any Inedoers want to have a sneak peek, leave your Github username and I'll invite as a contributor, or if you are OK with it being flipped over to public, I can do that and share the link.
I'd really rather like a set of eyes on it from your side so you can see my approach to things and get your blessing on it. Particularly around my use of stored procedures for some things! I'm.....unsure how "stable" those are in terms of them changing in future versions, etc.
I'VE DONE IT!!! What a wild ride. I ended up using the Stored Procedures for things. Going that route did the trick nicely. Though I did have to spend the weekend learning SQL to understand exactly what the hell is going on inside of Stored Procedures haha.
But, I've now got the code I need to go from 0 to a deployed ProGet instance programmatically. Still a bit of polishing left to do, and a whole heck of a lot of testing but I am super excited about this! Thank you very much for the Stored Procedure hint!!! I very much appreciate it.
@atripp said in Manipulate users using pgutil:
tech/marketing partnership
That is above my pay grade as well to iron out he specifics but it is in my wheelhouse to push for it internally :)
My end goal is to either replace Sonatype Nexus with Inedo ProGet or at least allow the choice of repository solution included with one of our canned Environments that we offer customers here at Chocolatey.
Right now we are able to fully provision a Sonatype Nexus server via the API so that the server has
Today I can get ~80% of the way there with Inedo, but until I can lock it down I can't start the work of baking it into our stuff. ProGet is the only repository in our testing that plays very very well with Chocolatey (thanks for that, btw), but without being able to natively do the security bits I'm afraid I can't move forward.
I hear you with the AD thing, but I can't guarantee AD will be available in all the environments we deploy too.
Our goal is to give them a turn-key "thing" they can be up and running with in under an hour and if they need to change things after the fact, they can work with our Support team to make those adjustments. Has worked out fairly well so far!
Are there plans to include the ability to work with user accounts via pgutil? For my use case I would like to be able to programmatically create a user account and assign it a read-only role to a specific feed such that I can configure and consume packages from the restricted feed on an endpoint. Currently there is not a way programmatically (at least that I can get to work) to do this sort of thing programmatically via PowerShell.
I've another thread on the forums which I was pulling on the HTTP api thread, but that led to a dead end as I can't craft appropriate JSON that the endpoint accepts, not for lack of trying haha.
We do have Chocolatey-AU (Automatic Updater) which will keep a version of a package up to date. Once I have everything setup it will look at whatever upstream I point at and keep the package updated for me automatically.
If you wanted to own the automation that's fine with me, but I have no problem with shoving the package source in my Chocolatey package's repository and using a Github Action to keep it updated (this is what I do today with other packages I maintain).
You've got my email, we can take this off the forums and hash out details of things privately if you prefer :)
Thanks, Alex! I have confirmed the request and have added one more person from my team as well so we have double coverage on this package.
As for the delisted packages, they won't show up for others on the community repository or via Chocolatey CLI once they are unlisted, but since you are the owner of those packages, they will continue to display on your profile. So all is right with the world there :)
If you're cool with it I may pick up packaging pgutil
as well.
I've reached out via the contact maintainers link on https://community.chocolatey.org/packages/proget but figured I'd post here as well for more visibility. I'd like to publish ProGet as a Chocolatey package. I've got the automation and packaging code working how I expect it should, but I can't publish it as there is already a package available.
While I could publish it under another package id....that seems icky to me so I'd rather be added as a maintainer and then setup a pipeline to keep it updated as you folks release new versions.
Let me know if you have any questions/comments/concerns!
I'm looking to publish a Chocolatey package to the Chocolatey Community Repository which will install the Free version of Inedo ProGet.
In order to do this, I am also going to publish a package for Inedo Hub, and take a dependency. Our Community Repository requires either a LICENSE.TXT include the license terms for an application being package, or a licenseUrl listed in the Nuspec. I cannot find the Licenser terms for either product. Perhaps I am just blind! :)
Can someone point me to those terms please so that I can properly publish these packages? I greatly appreciate it!
@steviecoaster said in Question about Salt_Bytes:
Sweet mother Mary, I got it! It was the content type. I was using
application/json
and it wantedapplication/x-ww-form-urlencoded
This code works properly to set the password for a user:
function Set-ProGetUserPassword { [Cmdletbinding()] Param( [Parameter(Mandatory)] [String] $Username, [Parameter(Mandatory)] [SecureString] $OldPassword, [Parameter(Mandatory)] [SecureString] $NewPassword ) begin { $confirm = Read-Host "Please re-enter your new password" -AsSecureString $identical = Compare-SecureString -SecureString1 $NewPassword -SecureString2 $confirm if (-not $identical) { Write-Error 'Passwords do not match!' break } } end { $unmanagedString = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($NewPassword) $plainTextPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($unmanagedString) [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($unmanagedString) # Clean up $iterations = 10000 $saltLength = 10 $hashLength = 20 $rfc2898 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new($plainTextPassword, $saltLength, $iterations) $passwordBytes = $rfc2898.GetBytes($hashLength) $saltBytes = $rfc2898.Salt $base64Password = [System.Convert]::ToBase64String($passwordBytes) $base64Salt = [System.Convert]::ToBase64String($saltBytes) $body = @{ User_Name = $Username Password_Bytes = $base64Password Salt_Bytes = $base64Salt } Write-Verbose $body $Params = @{ Slug = '/api/json/Users_SetPassword' Method = 'POST' Body = $body ContentType = 'application/x-www-form-urlencoded' } Invoke-ProGet @Params } }
Spoke to soon. This code functions and doesn't produce an error. However, the user cannot login with the credential set. More playing to do!
Sweet mother Mary, I got it! It was the content type. I was using application/json
and it wanted application/x-ww-form-urlencoded
This code works properly to set the password for a user:
function Set-ProGetUserPassword {
[Cmdletbinding()]
Param(
[Parameter(Mandatory)]
[String]
$Username,
[Parameter(Mandatory)]
[SecureString]
$OldPassword,
[Parameter(Mandatory)]
[SecureString]
$NewPassword
)
begin {
$confirm = Read-Host "Please re-enter your new password" -AsSecureString
$identical = Compare-SecureString -SecureString1 $NewPassword -SecureString2 $confirm
if (-not $identical) {
Write-Error 'Passwords do not match!'
break
}
}
end {
$unmanagedString = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($NewPassword)
$plainTextPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($unmanagedString)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($unmanagedString) # Clean up
$iterations = 10000
$saltLength = 10
$hashLength = 20
$rfc2898 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new($plainTextPassword, $saltLength, $iterations)
$passwordBytes = $rfc2898.GetBytes($hashLength)
$saltBytes = $rfc2898.Salt
$base64Password = [System.Convert]::ToBase64String($passwordBytes)
$base64Salt = [System.Convert]::ToBase64String($saltBytes)
$body = @{
User_Name = $Username
Password_Bytes = $base64Password
Salt_Bytes = $base64Salt
}
Write-Verbose $body
$Params = @{
Slug = '/api/json/Users_SetPassword'
Method = 'POST'
Body = $body
ContentType = 'application/x-www-form-urlencoded'
}
Invoke-ProGet @Params
}
}
In my playing around yesterday, I did try to convert that to base64 and it still produced the same error. I'm very perplexed! For my use case I really need this functionality so I'm hopeful I can find a solution.
Here's what I'm using currently, but it's complaining about Salt_Bytes. I'm fairly certain this is a PowerShell problem, but more eyes on it never hurt.
function Set-ProGetUserPassword {
[Cmdletbinding()]
Param(
[Parameter(Mandatory)]
[String]
$Username,
[Parameter(Mandatory)]
[SecureString]
$OldPassword,
[Parameter(Mandatory)]
[SecureString]
$NewPassword
)
begin {
$confirm = Read-Host "Please re-enter your new password" -AsSecureString
$identical = Compare-SecureString -SecureString1 $NewPassword -SecureString2 $confirm
if(-not $identical){
Write-Error 'Passwords do not match!'
break
}
}
end {
$unmanagedString = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($NewPassword)
$plainTextPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($unmanagedString)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($unmanagedString) # Clean up
$iterations = 10000
$saltLength = 10
$hashLength = 20
$rfc2898 = [System.Security.Cryptography.Rfc2898DeriveBytes]::new($plainTextPassword, $saltLength, $iterations)
$passwordBytes = $rfc2898.GetBytes($hashLength)
$saltBytes = $rfc2898.Salt
$base64Password = [System.Convert]::ToBase64String($passwordBytes)
$body = @{
User_Name = $UserName
Password_Bytes = $base64Password
Salt_Bytes = $saltBytes
}
Write-Verbose ($body | ConvertTo-Json)
$Params = @{
Slug = '/api/json/Users_SetPassword'
Method = 'POST'
Body = $body
}
Invoke-ProGet @Params
}
}
Re: Reset ProGet Admin Password via API
I've got code using the Native API which creates a user account and works wonders. But as I'm doing this programmatically I need to also generate a random password for the account, and set it. Seems pretty straightforward to do....but I'm unsure about what to do with the Salt_Bytes parameter available on the API. Any pointers would be outstanding.
Are we sure this is a ProGet problem and not a Docker problem? Assuming the asset is outside the container and you're trying to upload it into ProGet inside the container, you may be hitting a setting inside Docker.
@dean-houston thanks for that info! I think that'll do nicely. I'll have to play around with it to be sure, but from what I can tell it looks straightforward to implement. Thank you!
@dean-houston said in API ability to control Feed Access:
I believe it's possible to configure security with the Native API, but obviously not as easy. So I would explore that, it's probably close to what you wnat.
Do you have documentation/guidance on that? Whilst I'm a fan of DevTools in a browser and bending things to my will, I'd much more a fan of using documented non-hacky processes, particularly when I'm going to be potentially work in a customer environment.
This conversation may be best served taken off the forum and into more direct communications. There's a method to my madness here, and it doesn't really have any reason to be hashed out here publicly.
I'm looking to automate the setup and configuration of ProGet as much as I can. I've found the API to be really good so far, and Ive been able to automate 90% of the things that I require (I build a lot of repository instances). One thing that is super important to me is configuring Read-Only access to the feed to a particular user account, and configuring a Chocolatey client to use that credential to authenticate to the source.
There doesn't seem to be an API endpoint exposed to be able to configure local accounts programmatically. I know it's a non-trivial ask, but, could we get one?
I'm not sure the rules on posting links to gists, so if this breaks one, delete it. However, once I got my feet under me with setting ups HTTPS I went ahead and wrote some code so I can automate it. It assumes a few things, and could be improved but for my use case it is flexible where it needs to be. Enjoy!
https://gist.github.com/steviecoaster/0a2c0d4b09988dedf8e1df1844ec6b8a
HAZZAH! I got it!
The problem was in C:\ProgramData\Inedo\SharedConfig\ProGet.config
CertFile=""C:\proget_cert\cert.pfx""
Should be:
CertFile="C:\proget_cert\cert.pfx"
Pretty sure it's because I used "Copy as Path" and used the resulting string verbatim, which url ended my string, which wrote literally to the config file. Chaulk this one up to a Friday, but for folks landing here later with maybe the same thing: Don't put the path to your cert file in quotes.
I found some time to dig into this again this morning. I've switched to attempting to use my PFX file directly. It is still not functioning over SSL but the error in the browser has changed! I guess that's progress :)
For completeness here is what I've got in the settings:
It looks like some new info in the Event Logs has appeared, but without a debugger is not very useful to me:
Again, happy to provide any further info you may need.
Oh, it would be helpful of me to tell you that I'm running ProGet 2024.11 (Build 10)
Hey all,
Following https://docs.inedo.com/docs/installation/installing-on-iis/installation-windows-https-support to setup my instance to use SSL and it just....doesn't work.
I'm using a Let's Encrypt certificate installed in the LocalMachine\My (Personal) store. This certificate is valid, and in use by other working services on this server.
I don't see any issues within the web interface of proget:
Despite this, it seems I cannot reach the instance via https:
There is something in the logs though. Looks like the proget service running as Network Service doesn't have permission to the private key of my cert:
A fatal error occurred when attempting to access the TLS server credential private key. The error code returned from the cryptographic module is 0x8009030D. The internal error state is 10001.
The SSPI client process is ProGet.Service (PID: 8016).
I'm going to attempt to set it up with one of the other methods and see if I get it working that way, but any guidance on what seems the "easiest" way, would be helpful :)
If you're using Chocolatey at a version lower than 2.0.0 then there was only NuGet v2 support. Since 2.0.0 we've added support for NuGet v3. We also send chocolatey cli
in the headers of the request, so you would be able to monitor the traffic and determin which traffic is originating from that tool.
But as others mentioned, you wouldn't be able to determine much from the ProGet side.
Hi, me again. So, I'm a bit "stuck", and I'm sure it's silly but I can't find a good answer. I've got a Chocolatey Package that is downloading the latest offline ProGet installer, extracting it and attempting to install it silently.
I've had to do a bit of "magic" as the offline installer appears to not include hub.exe, so I've monkey-patched that in and I'm getting the following when actually invoking an install:
A fatal error was encountered. The library 'hostpolicy.dll' required to execute the application was not found in 'C:\Program Files\dotnet\'.
Failed to run as a self-contained app.
- The application was run as a self-contained app because 'C:\ProgramData\chocolatey\lib\proget\tools\InedoTemp\hub.runtimeconfig.json' did not specify a framework.
- If this should be a framework-dependent app, specify the appropriate framework in 'C:\ProgramData\chocolatey\lib\proget\tools\InedoTemp\hub.runtimeconfig.json'.
ERROR:
Running ['C:\ProgramData\chocolatey\lib\proget\tools\InedoTemp\hub.exe' install Proget:2024.9.0 --ConnectionString='Data Source=localhost\SQLEXPRESS; Integrated Security=True;' --TargetDirectory='C:\Program Files\ProGet' --UseIIS=true UseIntegratedWebServer=false] was not successful.
Exit code was '-2147450749' See log for possible error messages..
The install of proget was NOT successful.
Error while running 'C:\ProgramData\chocolatey\lib\proget\tools\chocolateyInstall.ps1'.
See log for details.
For completeness here is the chocolateyInstall.ps1 file I'm using. I appreciate there's some helper functions from Choco and one I wrote for getting the latest installer bits, but should be self-explanatory:
$ErrorActionPreference = 'Stop'
$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$helpers = Join-Path $toolsDir -ChildPath 'helpers.ps1'
$unzipPath = New-Item (Join-Path $toolsDir -ChildPath 'InedoTemp') -ItemType Directory
$hub = Join-Path $toolsDir -ChildPath 'hub.zip'
$pp = Get-PackageParameters
#Load helpers
. $helpers
# Download Offline Installer
$ProGet = Get-ProGetInstaller
$ProGetInstaller = Join-Path $toolsDir -ChildPath (Split-Path -Leaf $ProGet.Downloads)
$webFileArgs = @{
Packagename = $env:ChocolateyPackageName
FileFullPath = $ProGetInstaller
Url = $ProGet.Downloads
Checksum = '03CD312905CF735DE36B303FF28C63880B1B7AFD4A458D69280EAB88F546BD29'
checksumType = 'SHA256'
}
Get-ChocolateyWebFile @webFileArgs
# Extract Offline Installer
$unzipArgs = @{
Packagename = $env:ChocolateyPackageName
FileFullPath = $ProGetInstaller
Destination = $unzipPath
}
Get-ChocolateyUnzip @unzipArgs
#Copy Hub.exe to extracted directory
$unzipArgs = @{
Packagename = $env:ChocolateyPackageName
FileFullPath = $hub
Destination = $unzipPath
}
Get-ChocolateyUnzip @unzipArgs
#Install the sumbitch
#Set the connectionString
$ConnectionString = if ($pp['ConnectionString']) {
$pp['ConnectionString']
}
else {
'Data Source=localhost\SQLEXPRESS; Integrated Security=True;'
}
#Set the TargetDir
$ProGetDestination = if ($pp['InstallDir']) {
$pp['InstallDir']
}
else {
if (-not (Test-Path (Join-path $env:ProgramFiles -ChildPath 'ProGet'))) {
$null = New-Item (Join-path $env:ProgramFiles -ChildPath 'ProGet') -ItemType Directory
} else {
Join-Path $env:ProgramFiles -ChildPath 'ProGet'
}
}
$hubExe = Join-Path $unzipPath -ChildPath 'hub.exe'
$installArgs = @{
Statements = @('install', "Proget:$($env:ChocolateyPackageVersion)", "--ConnectionString='$ConnectionString'", "--TargetDirectory='$ProgetDestination'",'--UseIIS=true','UseIntegratedWebServer=false')
ExeToRun = $hubExe
validExitCodes = @(0)
}
Start-ChocolateyProcessAsAdmin @installArgs
#Secure with SSL?
Any pointers here would be superbly appreciated.
Ha, It was a goofy issue. I wasn't paying attention and had the Body defined as a string in my proxy function.
This is working nicely now!
And that is the part that wasn't clicking for me. I missed it was a self-extracting .exe Awesome, I can use Install-ChocolateyZipPackage as a base, and then go from there. Perfect!
I'm attempting to generate a Chocolatey package, which I can host on the Chocolatey Community Repository to install ProGet. I've found the OfflineInstaller downloads at https://my.inedo.com/downloads/installers?Product=ProGet and the documentation for silent installation at https://docs.inedo.com/docs/installation-windows-silent however this is all very confusing/conflicting with https://docs.inedo.com/docs/desktophub-offline, specifically this:
I can't find the clear path to be able to silently control the installation of ProGet through the use of Chocolatey. I am just....thoroughly confused at the minute :D