Skip to content

Commit

Permalink
Merge pull request #26 from svrooij/dev
Browse files Browse the repository at this point in the history
Latest version from DEV 2024.1
  • Loading branch information
svrooij authored Jan 3, 2024
2 parents cebee07 + cdb84f9 commit 6f15dab
Show file tree
Hide file tree
Showing 24 changed files with 312 additions and 150 deletions.
61 changes: 46 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ Take any (just msi installers for now) app from winget and upload it to Intune i
- Generating the needed script information
- Publish the app to Intune.

This application ~~is **Windows only** and~~ requires **Dotnet 7** to be installed on your computer. It's also a [beta application](#beta-application), so please report any issues you find.
A lot of commands run the `winget` command, so make sure you have the [App Installer](https://www.microsoft.com/p/app-installer/9nblggh4nns1) installed on your computer as well.
This application ~~is **Windows only** and~~ requires **Dotnet 7** to be installed on your computer. It's a [beta application](#beta-application), so please report any issues you find.
Some commands run the `winget` in the background and are thus Windows-only, make sure you have the [App Installer](https://www.microsoft.com/p/app-installer/9nblggh4nns1) installed on your computer if you want to use these commands.

The `package` and `publish` commands are cross-platform, and should work on any platform that supports dotnet 7. These commands no longer use the winget executable, which also means any other sources than `winget` are no longer supported.
The `msi` command is still windows only, as it uses the `Microsoft.Deployment.WindowsInstaller` package.

This application used to be Windows only, but recently the main functionality is ported to other platforms by reducing the [platform dependencies](https://svrooij.io/2023/10/24/create-intunewin-file/). This means that the `package` and `publish` commands should work on any platform that supports dotnet 7. The `msi` command is still windows only, as it uses the `Microsoft.Deployment.WindowsInstaller` package. Both the `package` and `publish` won't support other sources than `winget`, and will use my [open-source winget index](https://github.com/svrooij/winget-pkgs-index/), instead of running winget to get the required information.

[![LinkedIn Profile][badge_linkedin]][link_linkedin]
[![Link Mastodon][badge_mastodon]][link_mastodon]
Expand Down Expand Up @@ -51,7 +53,7 @@ The CLI has several commands, try them out yourself.

```Shell
Description:
Enhanced Winget CLI for automations
winget-intune by @svrooij allows you to package any winget app for Intune

Usage:
winget-intune [command] [options]
Expand All @@ -61,13 +63,14 @@ Options:
-?, -h, --help Show help and usage information

Commands:
install <packageId> Installs or upgrades a package
check <packageId> Check if a specific version in installed
info <packageId> Show package info as json
package <packageId> Package an app for Intune
publish <packageId> Publish an packaged app to Intune
msi <msiFile> Extract info from MSI file
about Information about this package and it's author
package <packageId> Package an app for Intune (cross platform)
publish <packageId> Publish a packaged app to Intune (cross platform)
about Information about this package and it's author (cross platform)
install <packageId> Installs or upgrades a package (Windows-only)
check <packageId> Check if a specific version in installed (Windows-only)
info <packageId> Show package info as json (Windows-only)
msi <msiFile> Extract info from MSI file (Windows-only)
```
### Package
Expand All @@ -80,10 +83,12 @@ It will also write a `app.json` file with all the information about the app, for
winget-intune package {PackageId} [--version {version}] [--source winget] --package-folder {PackageFolder}
```
> The `packageId` is case sensitive, so make sure you use the correct casing. Tip: Copy it from the result of the `winget search {name}` command.
> The `packageId` ~~is case sensitive, so make sure you use the correct casing~~ will be matches against any package in the open source [index](https://github.com/svrooij/winget-pkgs-index). Tip: Copy it from the result of the `winget search {name}` command.
This command will download the [content-prep-tool](https://github.com/Microsoft/Microsoft-Win32-Content-Prep-Tool) automatically, and use it to create the `intunewin` file.
In a future version this might be replaced with a custom implementation, but for now this works. The SHA265 hash of the installer is checked and compared to the one in the `winget` manifest, to make sure you won't package a tampered installer.
Upon downloading the installer, the SHA256 hash is checked against the one in the `winget` manifest, to make sure you won't package a tampered installer.

The packaging command uses an open-source & cross-platform [implementation](https://github.com/Svrooij/ContentPrep) of the Windows-only & closed source [content-prep-tool](https://github.com/Microsoft/Microsoft-Win32-Content-Prep-Tool), to allow cross-platform building of the packages.
This new implementation is available as a dotnet library and a PowerShell module, so if you're into Intune packaging, check it out.
### Publish
Expand All @@ -98,6 +103,30 @@ winget-intune publish {PackageId} --package-folder {PackageFolder} --tenant {Ten
winget-intune publish {PackageId} --package-folder {PackageFolder} --token {Token}
```

#### Assignement and categories

You can also assign the app to a group, and set the categories.

```Shell
# Add --category "Productivity" --category "Utilities" to the command to set the categories (use the exact name!)
winget-intune publish {PackageId} ... --category "Productivity" --category "Utilities"
# Add --available "group-guid" to make the app available to a group (use the guid of the group)
# Add --available "allusers" to make the app available to all users
# Instead of --available you can also use --required to make the app required for the group
# Or if you want to remove the app for that group, use --uninstall
winget-intune publish {PackageId}... --required "3bac8336-623f-46bf-bcab-b5c61e3e5b7a" --required "allusers"
winget-intune publish {PackageId}... --uninstall "3bac8336-623f-46bf-bcab-b5c61e3e5b7a" --uninstall "allusers"
```

#### Auto-package

You can also combine the `package` and `publish` command into one command, this will package the app and publish it to Intune. But this makes debugging harder, so when submitting issues, please don't use this option.
```Shell
winget-intune publish {PackageId}... --auto-package
```
## Library (soon)
I'm planning to release the actual intune specific code as a separate library, so you can use it in your own projects. This will be released as a separate package.
Expand All @@ -108,8 +137,10 @@ If you want to contribute to this project, please check out the [contributing](h

## Usefull information

- [Microsoft-Win32-Content-Prep-Tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)
- [Blog articles on Intune](https://svrooij.io/tags/intune/)
- Open-source [winget index](https://github.com/svrooij/winget-pkgs-index/)
- Open-source [PowerShell Content Prep](https://github.com/svrooij/contentprep)
- Closed source [Microsoft Win32 Content Prep tool](https://github.com/microsoft/Microsoft-Win32-Content-Prep-Tool)

[badge_blog]: https://img.shields.io/badge/blog-svrooij.io-blue?style=for-the-badge
[badge_linkedin]: https://img.shields.io/badge/LinkedIn-stephanvanrooij-blue?style=for-the-badge&logo=linkedin
Expand Down
40 changes: 37 additions & 3 deletions scripts/detection-with-version.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
$packageId = "7zip.7zip"
$version = "23.02.0.0"
$packageId = "Sonos.Controller"
$version = "73.0.41050"

$wingetOutput = & "winget" "list" "--id" $packageId "--exact" "--disable-interactivity" "--accept-source-agreements"
$transcriptionPath = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs\$packageId-detection.log"
Start-Transcript -Path $transcriptionPath -Force

Write-Host "Starting $packageId detection, log location: $transcriptionPath"

# Need to get the full path of winget, because detection script is run in a different context
Function Get-WingetCmd {

$WingetCmd = $null
#Get WinGet Path

try {
# Get Admin Context Winget Location
$WingetInfo = (Get-Item "$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller_*_8wekyb3d8bbwe\winget.exe").VersionInfo | Sort-Object -Property FileVersionRaw
# if multiple versions, pick most recent one
$WingetCmd = $WingetInfo[-1].FileName
}
catch {
#Get User context Winget Location
if (Test-Path "$env:LocalAppData\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe")
{
$WingetCmd ="$env:LocalAppData\Microsoft\WindowsApps\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\winget.exe"
}
}
Write-Host "Winget location: $WingetCmd"
return $WingetCmd
}

$wingetCmd = Get-WingetCmd
if ($null -eq $wingetCmd) {
Write-Host "winget not detected"
Exit 1
}

$wingetOutput = & $wingetCmd "list" "--id" $packageId "--exact" "--disable-interactivity" "--accept-source-agreements"

if($wingetOutput -is [array]) {
$lastRow = $wingetOutput[$wingetOutput.Length -1]
Expand Down
14 changes: 13 additions & 1 deletion src/Winget.CommunityRepository/WingetRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,23 @@ public WingetRepository(HttpClient? httpClient = null, ILogger<WingetRepository>
}

public async ValueTask<string?> GetLatestVersion(string packageId, CancellationToken cancellationToken = default)
{
var entry = await GetEntry(packageId, cancellationToken);
return entry?.Version;
}

public async ValueTask<string?> GetPackageId(string packageId, CancellationToken cancellationToken = default)
{
var entry = await GetEntry(packageId, cancellationToken);
return entry?.PackageId;
}

private async ValueTask<Models.WingetEntry?> GetEntry(string packageId, CancellationToken cancellationToken = default)
{
await LoadEntries(cancellationToken, false, cacheFile);

var entry = Entries!.FirstOrDefault(e => e.PackageId.Equals(packageId, StringComparison.OrdinalIgnoreCase));
return entry?.Version;
return entry;
}

public async ValueTask<IEnumerable<Models.WingetEntry>> SearchPackage(string query, CancellationToken cancellationToken = default)
Expand Down
2 changes: 1 addition & 1 deletion src/WingetIntune.Cli/Commands/AboutCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace WingetIntune.Commands;
internal class AboutCommand : Command
{
private const string name = "about";
private const string description = "Information about this package and it's author";
private const string description = "Information about this package and it's author (cross platform)";

public AboutCommand() : base(name, description)
{
Expand Down
2 changes: 1 addition & 1 deletion src/WingetIntune.Cli/Commands/CheckCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace WingetIntune.Commands;
internal class CheckCommand : Command
{
private const string name = "check";
private const string description = "Check if a specific version in installed";
private const string description = "Check if a specific version in installed (Windows-only)";

public CheckCommand() : base(name, description)
{
Expand Down
2 changes: 1 addition & 1 deletion src/WingetIntune.Cli/Commands/GenerateIndexCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ namespace WingetIntune.Commands;
internal class GenerateIndexCommand : Command
{
private const string name = "generate-index";
private const string description = "(hidden) Generates the index.json file for the repository";
private const string description = "(hidden) Generates the index.json file for the repository (cross platform)";
public GenerateIndexCommand() : base(name, description)
{
IsHidden = true;
Expand Down
2 changes: 1 addition & 1 deletion src/WingetIntune.Cli/Commands/InfoCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace WingetIntune.Commands;
internal class InfoCommand : Command
{
private const string name = "info";
private const string description = "Show package info as json";
private const string description = "Show package info as json (Windows-only)";

public InfoCommand() : base(name, description)
{
Expand Down
3 changes: 1 addition & 2 deletions src/WingetIntune.Cli/Commands/InstallOrUpgradeCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace WingetIntune.Commands
internal class InstallOrUpgradeCommand : Command
{
private const string name = "install";
private const string description = "Installs or upgrades a package";
private const string description = "Installs or upgrades a package (Windows-only)";

public InstallOrUpgradeCommand() : base(name, description)
{
Expand All @@ -36,7 +36,6 @@ public InstallOrUpgradeCommand() : base(name, description)
//AddOption(new Option<string>(new string[] { "--accept-package-matching", "-p" }, "Accept package matching"));
//AddOption(new Option<string>(new string[] { "--accept-package-modified", "-u" }, "Accept package modified"));
//AddOption(new Option<string>(new string[] { "--accept-package"}))

this.Handler = CommandHandler.Create(HandleCommand);
}

Expand Down
2 changes: 1 addition & 1 deletion src/WingetIntune.Cli/Commands/MsiCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace WingetIntune.Commands;
internal class MsiCommand : Command
{
private const string name = "msi";
private const string description = "Extract info from MSI file";
private const string description = "Extract info from MSI file (Windows-only)";

private Argument<string> msiFileArgument = new Argument<string>("msiFile", "Path to MSI file");

Expand Down
3 changes: 2 additions & 1 deletion src/WingetIntune.Cli/Commands/PackageCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace WingetIntune.Commands;
internal class PackageCommand : Command
{
private const string name = "package";
private const string description = "Package an app for Intune";
private const string description = "Package an app for Intune (cross platform)";

internal static readonly Option<string> TempFolderOption = new Option<string>("--temp-folder", () => Path.Combine(Path.GetTempPath(), "intunewin"), "Folder to store temporaty files")
{
Expand Down Expand Up @@ -69,6 +69,7 @@ private async Task<int> HandleCommand(PackageCommandOptions options, InvocationC
if (options.Version is null && options.Source == "winget" && !options.UseWinget)
{
logger.LogInformation("Getting latest version for {PackageId}", options.PackageId);
options.PackageId = (await repo.GetPackageId(options.PackageId, cancellationToken))!;
options.Version = await repo.GetLatestVersion(options.PackageId, cancellationToken);
}

Expand Down
5 changes: 2 additions & 3 deletions src/WingetIntune.Cli/Commands/PublishCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace WingetIntune.Commands;
internal class PublishCommand : Command
{
private const string name = "publish";
private const string description = "Publish a packaged app to Intune";
private const string description = "Publish a packaged app to Intune (cross platform)";

internal static readonly Option<string?> TenantOption = new Option<string?>("--tenant", "Tenant ID to use for authentication");
internal static readonly Option<string?> UsernameOption = new Option<string?>("--username", "Username to use for authentication");
Expand Down Expand Up @@ -63,6 +63,7 @@ private async Task<int> HandleCommand(PublishCommandOptions options, InvocationC
logger.LogInformation("Try loading latest version from package index");
var repo = host.Services.GetRequiredService<Winget.CommunityRepository.WingetRepository>();
//repo.UseRespository = true;
options.PackageId = (await repo.GetPackageId(options.PackageId, cancellationToken)) ?? options.PackageId;
options.Version = await repo.GetLatestVersion(options.PackageId, cancellationToken);
}
var tempInfo = await winget.GetPackageInfoAsync(options.PackageId, options.Version, options.Source, cancellationToken);
Expand All @@ -73,7 +74,6 @@ private async Task<int> HandleCommand(PublishCommandOptions options, InvocationC
}
if (options.AutoPackage && tempInfo.Source == PackageSource.Winget)
{

await intuneManager.GenerateInstallerPackage(options.TempFolder,
options.PackageFolder!,
tempInfo,
Expand Down Expand Up @@ -125,7 +125,6 @@ internal class PublishCommandOptions : WinGetRootCommand.DefaultOptions

public bool AutoPackage { get; set; }
public string TempFolder { get; set; }
public Uri ContentPrepToolUrl { get; set; }
public InstallerContext InstallerContext { get; set; }
public Architecture Architecture { get; set; }
}
10 changes: 7 additions & 3 deletions src/WingetIntune.Cli/Commands/WinGetRootCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ internal class WinGetRootCommand : RootCommand

public WinGetRootCommand()
{
Description = "Enhanced Winget CLI for automations";
Description = "winget-intune by @svrooij allows you to package any winget app for Intune";
// Cross platform commands
AddCommand(new PackageCommand());
AddCommand(new PublishCommand());
AddCommand(new AboutCommand());
AddCommand(new GenerateIndexCommand());

// Windows only command
AddCommand(new InstallOrUpgradeCommand());
AddCommand(new CheckCommand());
AddCommand(new InfoCommand());
AddCommand(new MsiCommand());
AddCommand(new AboutCommand());
AddCommand(new GenerateIndexCommand());

AddGlobalOption(VerboseOption);
AddGlobalOption(JsonOption);
}
Expand Down
10 changes: 8 additions & 2 deletions src/WingetIntune.Cli/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@
},
"WingetIntune.Cli package 1Password": {
"commandName": "Project",
"commandLineArgs": "publish AgileBits.1Password --package-folder c:\\tools\\packages --auto-package --available allusers",
"commandLineArgs": "package AgileBits.1Password --package-folder c:\\tools\\packages",
"environmentVariables": {
}
},
"WingetIntune.Cli package Notepadd++": {
"commandName": "Project",
"commandLineArgs": "package \"Notepad++.Notepad++\" --package-folder c:\\tools\\packages",
"environmentVariables": {
}
},
"WingetIntune.Cli info ohmyposh": {
"commandName": "Project",
"commandLineArgs": "info JanDeDobbeleer.OhMyPosh --source winget",
"commandLineArgs": "publish JanDeDobbeleer.OhMyPosh --package-folder c:\\tools\\packages --category Productivity --available allusers --auto-package --username [email protected]",
"environmentVariables": {
}
},
Expand Down
Loading

0 comments on commit 6f15dab

Please sign in to comment.