Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Latest version from DEV 2024.1 #26

Merged
merged 7 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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