Functions/New-IntuneWinPackage.psm1

function New-IntuneWinPackage {
    [cmdletbinding()]

    param (
        [String]$SourcePath,
        [String]$GraphAPI = "https://graph.microsoft.com/beta",
        [Boolean]$CreateGroup = $false,
        [Boolean]$UserInstallation = $false,
        [Boolean]$ServiceUI = $false
    )

    function WriteHost($Code, $Message) {
        if($code -eq 1){
            Write-Host "[Pending] | " $Message -f Magenta
        }
        if($code -eq 2){
            Write-Host "[Success] | " $Message -f DarkGreen
        }
        if($code -eq 3){
            Write-Host "[Failed] | " $Message -f DarkRed
        }
        if($code -eq 4){
            Write-Host "[Info] | " $Message -f DarkYellow
        }
    }
    ####################################################
    function Get-AuthToken {

        #Importing MSAL.PS
        if (!(Get-Module).Name.Contains('MSAL.PS')) {
            try {
                Write-Output -InputObject 'Loading Microsoft Authentication Library...'
                Import-Module -Name MSAL.PS -ErrorAction Stop
                Write-Output -InputObject 'Successfully loaded the Microsoft Authentication Library'
            }
            catch {
                Write-Output -InputObject 'We need to install it first...'
                Install-Module -Name MSAL.PS -Repository PSGallery -Scope CurrentUser
                Import-Module -Name MSAL.PS
                Write-Output -InputObject 'Successfully installed the Microsoft Authentication Library'
            }
        }

        #initiate the session variable
        $IntuneToken = @{
            Name        = 'IntuneToken'
            Description = 'Variable which will be used to managed the connection to Microsoft Graph'
            Scope       = 'Global'
            Visibility  = 'Public'
            Option      = 'None'
            Value       = @{
                Success         = $null
                Message         = ''
                Authentication  = $null
                ResultObject    = $null
                Exception       = $null
                Header          = $null
            }
        }
        Set-Variable @IntuneToken


        $IntuneAuthParam = @{
            clientId        = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
            Interactive     = $true
            redirectUri     = "d1ddf0e4-d672-4dae-b554-9d5bdfd93547"
        }

        try {
            $IntuneAuthentication = Get-MsalToken @IntuneAuthParam -ErrorAction Stop

            $Global:IntuneToken.ResultObject    = $IntuneAuthentication
            $Global:IntuneToken.Success         = $true
            $Global:IntuneToken.Message         = "Successfully retrieved bearer token"
            $Global:IntuneToken.Exception       = $_

            $IntuneAuthHeader = @{
                'Authorization' = "Bearer $($Global:IntuneToken.ResultObject.AccessToken)"
                'Content-type'  = 'application/json'
                'ExpiresOn'     = $Global:IntuneToken.ResultObject.ExpiresOn
            }
            $Global:IntuneToken.Header          = $IntuneAuthHeader
            Write-Output -InputObject 'Successfully connected to Intune'

            $Global:IntuneToken.Authentication = @{
                ClientID        = ($Global:IntuneToken.ResultObject.ClaimsPrincipal.Claims | Where-Object { $_.Type -eq 'aud'}).Value
                TenantID        = $Global:IntuneToken.ResultObject.TenantId
                Account         = $Global:IntuneToken.ResultObject.Account.Username
                RedirectUri     = $redirectUri

            }
            Write-Verbose -Message 'Stored the Authentication Information'
            return $Global:IntuneToken.Header

        }
        catch {
            $Global:IntuneToken.Success         = $false
            $Global:IntuneToken.Message         = "Error retrieving the bearer token"
            $Global:IntuneToken.Exception       = $_
        }
    }

    ####################################################
    function CloneObject($object){
        $stream = New-Object IO.MemoryStream;
        $formatter = New-Object Runtime.Serialization.Formatters.Binary.BinaryFormatter;
        $formatter.Serialize($stream, $object);
        $stream.Position = 0;
        $formatter.Deserialize($stream);
    }

    ####################################################
    function WriteHeaders($authToken){
        foreach ($header in $authToken.GetEnumerator()){
            if ($header.Name.ToLower() -eq "authorization"){
                continue;
            }
            Write-Host -ForegroundColor Gray "$($header.Name): $($header.Value)";
        }
    }
    ####################################################
    function MakeGetRequest($collectionPath){

        $uri = "$baseUrl$collectionPath";
        $request = "GET $uri";

        if ($logRequestUris) { Write-Host $request; }
        if ($logHeaders) { WriteHeaders $authToken; }

        try{
            Test-AuthToken
            $response = Invoke-RestMethod $uri -Method Get -Headers $authToken;
            $response;
        }
        catch{
            Write-Host -ForegroundColor Red $request;
            Write-Host -ForegroundColor Red $_.Exception.Message;
            throw;
        }
    }
    ####################################################
    function MakePatchRequest($collectionPath, $body){
        MakeRequest "PATCH" $collectionPath $body;
    }

    ####################################################
    function MakePostRequest($collectionPath, $body){
        MakeRequest "POST" $collectionPath $body;
    }
    ####################################################
    function MakeRequest($verb, $collectionPath, $body){

        $uri = "$baseUrl$collectionPath";
        $request = "$verb $uri";

        $clonedHeaders = CloneObject $authToken;
        $clonedHeaders["content-length"] = $body.Length;
        $clonedHeaders["content-type"] = "application/json";

        if ($logRequestUris) { Write-Host $request; }
        if ($logHeaders) { WriteHeaders $clonedHeaders; }
        if ($logContent) { Write-Host -ForegroundColor Gray $body; }



        try{

            Test-AuthToken
            $response = Invoke-RestMethod $uri -Method $verb -Headers $clonedHeaders -Body $body;
            $response;


        }
        catch{
            if ($_.Exception.Response.StatusCode.value__ -eq 401 -or $_.Exception.Response.StatusCode.value__ -eq 403){
                Write-Host "------ 401/403 - Unauthorized ------"
                WriteHost 3 "Access Denied! Check your Endpoint Permissions or PIM activation"
            }

            if ($_.Exception.Response.StatusCode.value__ -eq 400){
                Write-Host "------ 400 - Bad Request ------"
                WriteHost 3 "Check your Application.xml or Application.png!"
            }
            Write-Host -ForegroundColor Red $request;
            Write-Host -ForegroundColor Red $_.Exception.Message;
            throw;
        }


    }
    ####################################################
    function UploadAzureStorageChunk($sasUri, $id, $body){

        $uri = "$sasUri&comp=block&blockid=$id";
        $request = "PUT $uri";

        $iso = [System.Text.Encoding]::GetEncoding("iso-8859-1");
        $encodedBody = $iso.GetString($body);
        $headers = @{
            "x-ms-blob-type" = "BlockBlob"
        };

        if ($logRequestUris) { Write-Host $request; }
        if ($logHeaders) { WriteHeaders $headers; }

        try{
            Invoke-WebRequest $uri -Method Put -Headers $headers -Body $encodedBody | Out-Null
        }
        catch{
            Write-Host -ForegroundColor Red $request;
            Write-Host -ForegroundColor Red $_.Exception.Message;
            throw;
        }
    }
    ####################################################
    function FinalizeAzureStorageUpload($sasUri, $ids){

        $uri = "$sasUri&comp=blocklist";
        $request = "PUT $uri";

        $xml = '<?xml version="1.0" encoding="utf-8"?><BlockList>';
        foreach ($id in $ids){
            $xml += "<Latest>$id</Latest>";
        }
        $xml += '</BlockList>';

        if ($logRequestUris) { Write-Host $request; }
        if ($logContent) { Write-Host -ForegroundColor Gray $xml; }

        try{
            Invoke-RestMethod $uri -Method Put -Body $xml;
        }
        catch{
            Write-Host -ForegroundColor Red $request;
            Write-Host -ForegroundColor Red $_.Exception.Message;
            throw;
        }
    }
    ####################################################
    function UploadFileToAzureStorage($sasUri, $filepath, $fileUri){
        try{

            $chunkSizeInBytes = 1024l * 1024l * $azureStorageUploadChunkSizeInMb;
            $sasRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew()

            $fileSize = (Get-Item $filepath).length;
            $chunks = [Math]::Ceiling($fileSize / $chunkSizeInBytes);
            $reader = New-Object System.IO.BinaryReader([System.IO.File]::Open($filepath, [System.IO.FileMode]::Open));
            $reader.BaseStream.Seek(0, [System.IO.SeekOrigin]::Begin);
            $ids = @();

            for ($chunk = 0; $chunk -lt $chunks; $chunk++){

                $id = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($chunk.ToString("0000")));
                $ids += $id;

                $start = $chunk * $chunkSizeInBytes;
                $length = [Math]::Min($chunkSizeInBytes, $fileSize - $start);
                $bytes = $reader.ReadBytes($length);

                $currentChunk = $chunk + 1;

                Write-Progress -Activity "Uploading File to Azure Storage" -status "Uploading chunk $currentChunk of $chunks" `
                -percentComplete ($currentChunk / $chunks*100)

                UploadAzureStorageChunk $sasUri $id $bytes;

                if ($currentChunk -lt $chunks -and $sasRenewalTimer.ElapsedMilliseconds -ge 450000){
                    RenewAzureStorageUpload $fileUri
                    $sasRenewalTimer.Restart();
                }
            }

            Write-Progress -Completed -Activity "Uploading File to Azure Storage"
            $reader.Close();
        }
        finally {
            if ($null -ne $reader) { $reader.Dispose(); }
        }

        FinalizeAzureStorageUpload $sasUri $ids;
    }
    ####################################################
    function RenewAzureStorageUpload($fileUri){
        $renewalUri = "$fileUri/renewUpload";
        $actionBody = "";
        MakePostRequest $renewalUri $actionBody;
        WaitForFileProcessing $fileUri "AzureStorageUriRenewal" $azureStorageRenewSasUriBackOffTimeInSeconds;
    }
    ####################################################
    function WaitForFileProcessing($fileUri, $stage){

        $attempts= 600;
        $waitTimeInSeconds = 10;

        $successState = "$($stage)Success";
        $pendingState = "$($stage)Pending";

        $file = $null;
        while ($attempts -gt 0)
        {
            $file = MakeGetRequest $fileUri;

            if ($file.uploadState -eq $successState){
                break;
            }
            elseif ($file.uploadState -ne $pendingState){
                Write-Host -ForegroundColor Red $_.Exception.Message;
                throw "File upload state is not success: $($file.uploadState)";
            }

            Start-Sleep $waitTimeInSeconds;
            $attempts--;
        }

        if ($null -eq $file -or $file.uploadState -ne $successState){
            throw "File request did not complete in the allotted time.";
        }

        $file;
    }
    ####################################################
    function GetWin32AppBody(){
    param
    (
        [parameter(Mandatory=$true,ParameterSetName = "MSI",Position=1)]
        [Switch]$MSI,

        [parameter(Mandatory=$true,ParameterSetName = "EXE",Position=1)]
        [Switch]$EXE,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$displayName,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$publisher,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$description,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$filename,

        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$SetupFileName,

        [parameter(Mandatory=$true)]
        [ValidateSet('system','user')]
        $installExperience = "system",

        [parameter(Mandatory=$true,ParameterSetName = "EXE")]
        [ValidateNotNullOrEmpty()]
        $installCommandLine,

        [parameter(Mandatory=$true,ParameterSetName = "EXE")]
        [ValidateNotNullOrEmpty()]
        $uninstallCommandLine,

        [parameter(Mandatory=$true,ParameterSetName = "MSI")]
        [ValidateNotNullOrEmpty()]
        $MsiPackageType,

        [parameter(Mandatory=$true,ParameterSetName = "MSI")]
        [ValidateNotNullOrEmpty()]
        $MsiProductCode,

        [parameter(Mandatory=$false,ParameterSetName = "MSI")]
        $MsiProductName,

        [parameter(Mandatory=$true,ParameterSetName = "MSI")]
        [ValidateNotNullOrEmpty()]
        $MsiProductVersion,

        [parameter(Mandatory=$false,ParameterSetName = "MSI")]
        $MsiPublisher,

        [parameter(Mandatory=$true,ParameterSetName = "MSI")]
        [ValidateNotNullOrEmpty()]
        $MsiRequiresReboot,

        [parameter(Mandatory=$true,ParameterSetName = "MSI")]
        [ValidateNotNullOrEmpty()]
        $MsiUpgradeCode
    )



    $AllowUninstall = $false #####falls App eine K-Software "Deinstallation ermöglichen"
        if ($($XMLsoftwaretype).contains("K-Software")) {
            $AllowUninstall = $true
    }

    $LogoBase64 = [Convert]::ToBase64String((Get-Content -Path $SourcePath\Application.png -Encoding Byte))
        if($MSI){ #löschen

            $body = @{ "@odata.type" = "#microsoft.graph.win32LobApp" };
            $body.applicableArchitectures = "x64,x86";
            $body.description = $description;
            $body.developer = "";
            $body.displayName = $displayName;
            $body.fileName = $filename;
            $body.installCommandLine = "msiexec /i `"$SetupFileName`""
            $body.installExperience = @{"runAsAccount" = "$installExperience"};
            $body.informationUrl = $null;
            $body.isFeatured = $false;
            $body.minimumSupportedOperatingSystem = @{"v10_1607" = $true};
            $body.msiInformation = @{
                "packageType" = "$MsiPackageType";
                "productCode" = "$MsiProductCode";
                "productName" = "$MsiProductName";
                "productVersion" = "$MsiProductVersion";
                "publisher" = "$MsiPublisher";
                "requiresReboot" = "$MsiRequiresReboot";
                "upgradeCode" = "$MsiUpgradeCode"
            };
            $body.notes = "";
            $body.allowAvailableUninstall = "$AllowUninstall";
            $body.owner = "";
            $body.privacyInformationUrl = $null;
            $body.publisher = $publisher;
            $body.runAs32bit = $false;
            $body.setupFilePath = $SetupFileName;
            $body.largeIcon = @{
                "@odata.type" = "microsoft.graph.mimeContent";
                "type" = "binary";
                "value" = "$LogoBase64";
            };
            $body.uninstallCommandLine = "msiexec /x `"$MsiProductCode`""
        }

        elseif($EXE){
            if ($UserInstallation){
                $installExperience = "user"
            }
            $date = Get-Date -Format "MM/dd/yyyy"

            $body = @{ "@odata.type" = "#microsoft.graph.win32LobApp" };
            $body.description = "Created by: " + $XMLScriptAuthor;
            $body.developer = "WAGNER AG";
            $body.displayName = $XMLvendor + " " + $XMLname + " " + $XMLversion + " " + $XMLlanguage + " " + $XMLRevision;
            $body.fileName = $filename;
            $body.installCommandLine = "$installCommandLine"
            $body.installExperience = @{"runAsAccount" = "$installExperience"};
            $body.informationUrl = $null;
            $body.isFeatured = $false;
            $body.minimumSupportedOperatingSystem = @{"v10_1607" = $true};
            $body.msiInformation = $null;
            $body.notes = $XMLsoftwaretype;
            $body.allowAvailableUninstall ="$AllowUninstall";
            $body.owner = "WAGNER AG";
            $body.privacyInformationUrl = $null;
            $body.publisher = $publisher;
            $body.runAs32bit = $false;
            $body.setupFilePath = $SetupFileName;
            $body.largeIcon = @{
                "@odata.type" = "microsoft.graph.mimeContent";
                "type" = "binary";
                "value" = "$LogoBase64";
            };
            $body.uninstallCommandLine = "$uninstallCommandLine"

        }
        return $body
    }
    ####################################################
    function GetAppFileBody($name, $size, $sizeEncrypted, $manifest){
        $body = @{ "@odata.type" = "#microsoft.graph.mobileAppContentFile" };
        $body.name = $name;
        $body.size = $size;
        $body.sizeEncrypted = $sizeEncrypted;
        $body.manifest = $manifest;
        $body.isDependency = $false;
        return $body
    }
    ####################################################
    function GetAppCommitBody($contentVersionId, $LobType){
        $body = @{ "@odata.type" = "#$LobType" };
        $body.committedContentVersion = $contentVersionId;
        return $body
    }
    ####################################################
    Function Test-SourceFile(){
        param
        (
            [parameter(Mandatory=$true)]
            [ValidateNotNullOrEmpty()]
            $SourceFile
        )

        try {
            if(!(test-path "$SourceFile")){
                Write-Host
                Write-Host "Source File '$sourceFile' doesn't exist..." -ForegroundColor Red
                throw
                }
            }
        catch {
            Write-Host -ForegroundColor Red $_.Exception.Message;
            Write-Host
            break
        }
    }
    ####################################################
    Function New-DetectionRule(){
        [cmdletbinding()]

        param
        (
            [parameter(Mandatory=$true,ParameterSetName = "PowerShell",Position=1)]
            [Switch]$PowerShell,

            [parameter(Mandatory=$true,ParameterSetName = "MSI",Position=1)]
            [Switch]$MSI,

            [parameter(Mandatory=$true,ParameterSetName = "File",Position=1)]
            [Switch]$File,

            [parameter(Mandatory=$true,ParameterSetName = "Registry",Position=1)]
            [Switch]$Registry,

            [parameter(Mandatory=$true,ParameterSetName = "PowerShell")]
            [ValidateNotNullOrEmpty()]
            [String]$ScriptFile,

            [parameter(Mandatory=$true,ParameterSetName = "PowerShell")]
            [ValidateNotNullOrEmpty()]
            $enforceSignatureCheck,

            [parameter(Mandatory=$true,ParameterSetName = "PowerShell")]
            [ValidateNotNullOrEmpty()]
            $runAs32Bit,

            [parameter(Mandatory=$true,ParameterSetName = "MSI")]
            [ValidateNotNullOrEmpty()]
            [String]$MSIproductCode,

            [parameter(Mandatory=$true,ParameterSetName = "File")]
            [ValidateNotNullOrEmpty()]
            [String]$Path,

            [parameter(Mandatory=$true,ParameterSetName = "File")]
            [ValidateNotNullOrEmpty()]
            [string]$FileOrFolderName,

            [parameter(Mandatory=$true,ParameterSetName = "File")]
            [ValidateSet("notConfigured","exists","modifiedDate","createdDate","version","sizeInMB")]
            [string]$FileDetectionType,

            [parameter(Mandatory=$false,ParameterSetName = "File")]
            $FileDetectionValue = $null,

            [parameter(Mandatory=$true,ParameterSetName = "File")]
            [ValidateSet("True","False")]
            [string]$check32BitOn64System = "False",

            [parameter(Mandatory=$true,ParameterSetName = "Registry")]
            [ValidateNotNullOrEmpty()]
            [String]$RegistryKeyPath,

            [parameter(Mandatory=$true,ParameterSetName = "Registry")]
            [ValidateSet("notConfigured","exists","doesNotExist","string","integer","version")]
            [string]$RegistryDetectionType,

            [parameter(Mandatory=$false,ParameterSetName = "Registry")]
            [ValidateNotNullOrEmpty()]
            [String]$RegistryValue,

            [parameter(Mandatory=$true,ParameterSetName = "Registry")]
            [ValidateSet("True","False")]
            [string]$check32BitRegOn64System = "False"
        )

        if($PowerShell){
            if(!(Test-Path "$ScriptFile")){
                Write-Host
                Write-Host "Could not find file '$ScriptFile'..." -ForegroundColor Red
                Write-Host "Script can't continue..." -ForegroundColor Red
                Write-Host
                break
            }
            $ScriptContent = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes("$ScriptFile"));
            $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppPowerShellScriptDetection" }
            $DR.enforceSignatureCheck = $false;
            $DR.runAs32Bit = $false;
            $DR.scriptContent =  "$ScriptContent";
        }

        elseif($MSI){
            $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppProductCodeDetection" }
            $DR.productVersionOperator = "notConfigured";
            $DR.productCode = "$MsiProductCode";
            $DR.productVersion =  $null;
        }

        elseif($File){
            $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppFileSystemDetection" }
            $DR.check32BitOn64System = "$check32BitOn64System";
            $DR.detectionType = "$FileDetectionType";
            $DR.detectionValue = $FileDetectionValue;
            $DR.fileOrFolderName = "$FileOrFolderName";
            $DR.operator =  "notConfigured";
            $DR.path = "$Path"
        }

        elseif($Registry){
            $DR = @{ "@odata.type" = "#microsoft.graph.win32LobAppRegistryDetection" }
            $DR.check32BitOn64System = "$check32BitRegOn64System";
            $DR.detectionType = "$RegistryDetectionType";
            $DR.detectionValue = "Installed";
            $DR.keyPath = "$RegistryKeyPath";
            $DR.operator = "equal";
            $DR.valueName = "$RegistryValue"
        }
        return $DR
    }
    ####################################################
    function Get-DefaultReturnCodes(){
        @{"returnCode" = 0;"type" = "success"}, `
        @{"returnCode" = 1707;"type" = "success"}, `
        @{"returnCode" = 3010;"type" = "softReboot"}, `
        @{"returnCode" = 1641;"type" = "hardReboot"}, `
        @{"returnCode" = 1618;"type" = "retry"}
    }
    ####################################################
    function New-ReturnCode(){
        param
        (
            [parameter(Mandatory=$true)]
            [int]$returnCode,
            [parameter(Mandatory=$true)]
            [ValidateSet('success','softReboot','hardReboot','retry')]
            $type
        )
        @{"returnCode" = $returnCode;"type" = "$type"}
    }
    ####################################################
    Function Get-IntuneWinXML(){
        param
        (
            [Parameter(Mandatory=$true)]
            $SourceFile,

            [Parameter(Mandatory=$true)]
            $fileName,

            [Parameter(Mandatory=$false)]
            [ValidateSet("false","true")]
            [string]$removeitem = "true"
        )

        Test-SourceFile "$SourceFile"
        $Directory = [System.IO.Path]::GetDirectoryName("$SourceFile")
        Add-Type -Assembly System.IO.Compression.FileSystem
        $zip = [IO.Compression.ZipFile]::OpenRead("$SourceFile")
        $zip.Entries | Where-Object {$_.Name -like "$filename" } | ForEach-Object {
            [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$Directory\$filename", $true)
        }

        $zip.Dispose()
        [xml]$IntuneWinXML = Get-Content "$Directory\$filename"
        return $IntuneWinXML
        if($removeitem -eq "true"){ remove-item "$Directory\$filename" }
    }
    ####################################################

    Function Get-IntuneWinFile(){
        param
            (
            [Parameter(Mandatory=$true)]
            $SourceFile,

            [Parameter(Mandatory=$true)]
            $fileName,

            [Parameter(Mandatory=$false)]
            [string]$Folder = "win32"
        )

        $Directory = [System.IO.Path]::GetDirectoryName("$SourceFile")
        if(!(Test-Path "$Directory\$folder")){
            New-Item -ItemType Directory -Path "$Directory" -Name "$folder" | Out-Null
        }

        Add-Type -Assembly System.IO.Compression.FileSystem
        $zip = [IO.Compression.ZipFile]::OpenRead("$SourceFile")
        $zip.Entries | Where-Object {$_.Name -like "$filename" } | ForEach-Object {
            [System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$Directory\$folder\$filename", $true)
        }

        $zip.Dispose()
        return "$Directory\$folder\$filename"
        if($removeitem -eq "true"){ remove-item "$Directory\$filename" }
    }
    ####################################################
    function Add-Win32Lob(){
        [cmdletbinding()]

        param
        (
            [parameter(Mandatory=$true,Position=1)]
            [ValidateNotNullOrEmpty()]
            [string]$SourceFile,

            [parameter(Mandatory=$false)]
            [ValidateNotNullOrEmpty()]
            [string]$displayName,

            [parameter(Mandatory=$true,Position=2)]
            [ValidateNotNullOrEmpty()]
            [string]$publisher,

            [parameter(Mandatory=$true,Position=3)]
            [ValidateNotNullOrEmpty()]
            [string]$description,

            [parameter(Mandatory=$true,Position=4)]
            [ValidateNotNullOrEmpty()]
            $detectionRules,

            [parameter(Mandatory=$true,Position=5)]
            [ValidateNotNullOrEmpty()]
            $returnCodes,

            [parameter(Mandatory=$false,Position=6)]
            [ValidateNotNullOrEmpty()]
            [string]$installCmdLine,

            [parameter(Mandatory=$false,Position=7)]
            [ValidateNotNullOrEmpty()]
            [string]$uninstallCmdLine,

            [parameter(Mandatory=$false,Position=8)]
            [ValidateSet('system','user')]
            $installExperience = "system"
        )
        try    {
            $LOBType = "microsoft.graph.win32LobApp"

            Write-Host "Testing if SourceFile '$SourceFile' Path is valid..." -ForegroundColor Yellow
            Test-SourceFile "$SourceFile"

            Write-Host
            Write-Host "Creating JSON data to pass to the service..." -ForegroundColor Yellow

            $DetectionXML = Get-IntuneWinXML "$SourceFile" -fileName "detection.xml"

            if($displayName){ $DisplayName = $displayName }
            else { $DisplayName = $DetectionXML.ApplicationInfo.Name }

            $FileName = $DetectionXML.ApplicationInfo.FileName
            $SetupFileName = $DetectionXML.ApplicationInfo.SetupFile
            $Ext = [System.IO.Path]::GetExtension($SetupFileName)

            if((($Ext).contains("msi") -or ($Ext).contains("Msi")) -and (!$installCmdLine -or !$uninstallCmdLine)){

                $MsiExecutionContext = $DetectionXML.ApplicationInfo.MsiInfo.MsiExecutionContext
                $MsiPackageType = "DualPurpose";
                if($MsiExecutionContext -eq "System") { $MsiPackageType = "PerMachine" }
                elseif($MsiExecutionContext -eq "User") { $MsiPackageType = "PerUser" }

                $MsiProductCode = $DetectionXML.ApplicationInfo.MsiInfo.MsiProductCode
                $MsiProductVersion = $DetectionXML.ApplicationInfo.MsiInfo.MsiProductVersion
                $MsiPublisher = $DetectionXML.ApplicationInfo.MsiInfo.MsiPublisher
                $MsiRequiresReboot = $DetectionXML.ApplicationInfo.MsiInfo.MsiRequiresReboot
                $MsiUpgradeCode = $DetectionXML.ApplicationInfo.MsiInfo.MsiUpgradeCode

                if($MsiRequiresReboot -eq "false"){ $MsiRequiresReboot = $false }
                elseif($MsiRequiresReboot -eq "true"){ $MsiRequiresReboot = $true }
                $mobileAppBody = GetWin32AppBody `
                    -MSI `
                    -displayName "$DisplayName" `
                    -publisher "$publisher" `
                    -description $description `
                    -filename $FileName `
                    -SetupFileName "$SetupFileName" `
                    -installExperience $installExperience `
                    -MsiPackageType $MsiPackageType `
                    -MsiProductCode $MsiProductCode `
                    -MsiProductName $displayName `
                    -MsiProductVersion $MsiProductVersion `
                    -MsiPublisher $MsiPublisher `
                    -MsiRequiresReboot $MsiRequiresReboot `
                    -MsiUpgradeCode $MsiUpgradeCode
            }
            else {
                $mobileAppBody = GetWin32AppBody -EXE -displayName "$DisplayName" -publisher "$publisher" `
                -description $description -filename $FileName -SetupFileName "$SetupFileName" `
                -installExperience $installExperience -installCommandLine $installCmdLine `
                -uninstallCommandLine $uninstallcmdline
            }

            if($DetectionRules.'@odata.type' -contains "#microsoft.graph.win32LobAppPowerShellScriptDetection" -and @($DetectionRules).'@odata.type'.Count -gt 1){
                Write-Host
                Write-Warning "A Detection Rule can either be 'Manually configure detection rules' or 'Use a custom detection script'"
                Write-Warning "It can't include both..."
                Write-Host
                break
            }

            else {
                $mobileAppBody | Add-Member -MemberType NoteProperty -Name 'detectionRules' -Value $detectionRules
            }

            if($returnCodes){
                $mobileAppBody | Add-Member -MemberType NoteProperty -Name 'returnCodes' -Value @($returnCodes)
            }

            else {
                Write-Host
                Write-Warning "Intunewin file requires ReturnCodes to be specified"
                Write-Warning "If you want to use the default ReturnCode run 'Get-DefaultReturnCodes'"
                Write-Host
                break
            }

            Write-Host
            Write-Host "Creating application in Intune..." -ForegroundColor Yellow
            try {
                $mobileApp = MakePostRequest "mobileApps" ($mobileAppBody | ConvertTo-Json);
            }
            catch {
                WriteHost 1 "Clear Session..."
                powershell -nologo
                exit
            }

            Write-Host
            Write-Host "Creating Content Version in the service for the application..." -ForegroundColor Yellow
            $global:appId = $mobileApp.id;
            $contentVersionUri = "mobileApps/$appId/$LOBType/contentVersions";
            $contentVersion = MakePostRequest $contentVersionUri "{}";

            Write-Host
            Write-Host "Getting Encryption Information for '$SourceFile'..." -ForegroundColor Yellow

            $encryptionInfo = @{};
            $encryptionInfo.encryptionKey = $DetectionXML.ApplicationInfo.EncryptionInfo.EncryptionKey
            $encryptionInfo.macKey = $DetectionXML.ApplicationInfo.EncryptionInfo.macKey
            $encryptionInfo.initializationVector = $DetectionXML.ApplicationInfo.EncryptionInfo.initializationVector
            $encryptionInfo.mac = $DetectionXML.ApplicationInfo.EncryptionInfo.mac
            $encryptionInfo.profileIdentifier = "ProfileVersion1";
            $encryptionInfo.fileDigest = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigest
            $encryptionInfo.fileDigestAlgorithm = $DetectionXML.ApplicationInfo.EncryptionInfo.fileDigestAlgorithm

            $global:fileEncryptionInfo = @{};
            $global:fileEncryptionInfo.fileEncryptionInfo = $encryptionInfo;

            $IntuneWinFile = Get-IntuneWinFile "$SourceFile" -fileName "$filename"

            [int64]$Size = $DetectionXML.ApplicationInfo.UnencryptedContentSize
            $EncrySize = (Get-Item "$IntuneWinFile").Length

            Write-Host
            Write-Host "Creating a new file entry in Azure for the upload..." -ForegroundColor Yellow
            $contentVersionId = $contentVersion.id;
            $fileBody = GetAppFileBody "$FileName" $Size $EncrySize $null;
            $filesUri = "mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files";
            $file = MakePostRequest $filesUri ($fileBody | ConvertTo-Json);

            Write-Host
            Write-Host "Waiting for the file entry URI to be created..." -ForegroundColor Yellow
            $fileId = $file.id;
            $fileUri = "mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files/$fileId";
            $file = WaitForFileProcessing $fileUri "AzureStorageUriRequest";

            Write-Host
            Write-Host "Uploading file to Azure Storage..." -f Yellow

            $sasUri = $file.azureStorageUri;
            $global:sasUriClean = [uri]$sasUri
            $global:sasUriClean = "https://"+$global:sasUriClean.Host+$global:sasUriClean.AbsolutePath
            UploadFileToAzureStorage $file.azureStorageUri "$IntuneWinFile" $fileUri;

            [System.IO.Path]::GetDirectoryName("$IntuneWinFile")
            Remove-Item "$IntuneWinFile" -Force

            Write-Host
            Write-Host "Committing the file into Azure Storage..." -ForegroundColor Yellow
            $commitFileUri = "mobileApps/$appId/$LOBType/contentVersions/$contentVersionId/files/$fileId/commit";
            MakePostRequest $commitFileUri ($global:fileEncryptionInfo | ConvertTo-Json);

            Write-Host
            Write-Host "Waiting for the service to process the commit file request..." -ForegroundColor Yellow
            $file = WaitForFileProcessing $fileUri "CommitFile";

            Write-Host
            Write-Host "Committing the file into Azure Storage..." -ForegroundColor Yellow
            $commitAppUri = "mobileApps/$appId";
            $commitAppBody = GetAppCommitBody $contentVersionId $LOBType;
            MakePatchRequest $commitAppUri ($commitAppBody | ConvertTo-Json);

            return $mobileApp.id
        }
        catch {
            Write-Host "";
            Write-Host -ForegroundColor Red "Aborting with exception: $($_.Exception.ToString())";
        }
    }
    ####################################################
    Function Test-AuthToken(){
        [CmdletBinding()]
        param(

        )

        # The variable isn't even loaded in the current session
        if ((Get-Variable -Scope Global | Where-Object {$_.Name -eq "IntuneToken"}).count -lt 1) {
            Write-Output -InputObject 'it seems that the variable is not even loaded in the current session'
            Get-AuthToken
        }
        Write-Verbose -Message 'The proper variable does already exist'


        # There is no Authentication Header for Intune
        if ($null -eq ($Global:IntuneToken.Header)) {
            Write-Output -InputObject 'it seems that there does not exist a proper authentication header'
            Get-AuthToken
        }
        Write-Verbose -Message 'There is a proper authentication header available'

        # since the current date is greater than the expiry time of the token, we need to get a new one
        $TokenExpires = ($Global:IntuneToken.ResultObject.ExpiresOn.DateTime - (Get-Date).ToUniversalTime()).TotalMinutes
        if ($TokenExpires -le 0) {
            Write-Output -InputObject "it seem's that your token has expired $TokenExpires minutes ago. Trying to refresh the Token"
            $Global:IntuneToken.ResultObject = Get-Msaltoken -ForceRefresh -ClientId $Global:IntuneToken.Authentication.ClientID -RedirectUri $Global:IntuneToken.Authentication.RedirectUri
            $Global:IntuneToken.Header.Authorization = $Global:IntuneToken.ResultObject.AccessToken
        }
        Write-Verbose -Message "The authentication token is still valid for $TokenExpires"


        $Global:authHeader = $Global:IntuneToken.Header
        $Global:authHeader | Out-Null

        $Global:authToken = $Global:IntuneToken.Header
        $Global:authToken | Out-Null
    }

    ####################################################
    #---------------------------------------------------------[Get XML-File]--------------------------------------------------------
    function Get-XMLFile() {
        WriteHost 1 "Get XML-File informations"
        [xml]$ApplicationXML = Get-Content $SourcePath\Application.xml
        $Global:XMLprefix = $ApplicationXML.Application.Prefix
        $Global:XMLvendor = $ApplicationXML.Application.vendor
        $Global:XMLname = $ApplicationXML.Application.name
        $Global:XMLRevision = $ApplicationXML.Application.Revision
        $Global:XMLversion = $ApplicationXML.Application.version
        $Global:XMLlanguage = $ApplicationXML.Application.language
        $Global:XMLsoftwaretype = $ApplicationXML.Application.SoftwareType
        $Global:XMLScriptAuthor = $ApplicationXML.Application.ScriptAuthor


        WriteHost 2 "Successfully created registry key"
        if ($UserInstallation){
            $Global:SoftwareRegKey = "Computer\HKEY_CURRENT_USER\SOFTWARE\Wagner\SWD\"+$XMLprefix+$XMLvendor+" "+$XMLname+" "+$XMLversion+" "+$XMLlanguage+" "+$XMLRevision
        }else {
            $Global:SoftwareRegKey = "Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Wagner\SWD\"+$XMLprefix+$XMLvendor+" "+$XMLname+" "+$XMLversion+" "+$XMLlanguage+" "+$XMLRevision
        }

        Write-Host "----------------------"
        $Global:SoftwareRegKey
        Write-Host "----------------------"

        WriteHost 2 "Successfully created AADGroup name"
        $Global:AADGroupName = "SG_INTUNE_App_"+$XMLvendor+"-"+$XMLname+"-"+$XMLversion+"-"+$XMLlanguage
        Write-Host "----------------------"
        $Global:AADGroupName
        Write-Host "----------------------"
    }
    #---------------------------------------------------------[Create IntuneWIN App]--------------------------------------------------------
    function New-IntuneWinApp() {
        WriteHost 1 "Create IntuneWinApp..."
        New-Item -ItemType Directory -Path "$temp\$XMLname" | Out-Null
        &"$PSScriptRoot\IntuneWinAppUtil.exe" -c $SourcePath -s Deploy-Application.exe -o "$temp\$XMLname" -Force
        WriteHost 2 "IntuneWinApp created successfully"
    }
    #---------------------------------------------------------[Check AAD Group]--------------------------------------------------------
    function Get-AADGroup() {
        $GroupExists = $false
        WriteHost 1 ("Check if "+$AADGroupName+" exists...")
        $GraphAPI = $GraphAPI+'/groups'
        $SearchAADGroup = Invoke-RestMethod -Method "GET" -Uri $GraphAPI -Headers $authHeader
        $SearchAADGroupNames = $SearchAADGroup.value.displayName
        foreach ($SearchAADGroupName in $SearchAADGroupNames) {
            if ($SearchAADGroupName -eq $AADGroupName) {
                $GroupExists = $true
            }
        }
        return $GroupExists
    }
    #---------------------------------------------------------[Create AAD-Group]--------------------------------------------------------
    function Add-AADGroup() {
        WriteHost 1 ("Trying to create AAD Group "+$AADGroupName+"...")
        $params = @{
            "description"= "Intune Software Distribution Group";
            "displayName"= $AADGroupName;
            "mailEnabled"= $false;
            "securityEnabled" = $true;
            "mailNickname" = "Intune";
        }
        $params = ($params|ConvertTo-Json)
        $GraphAPI = $GraphAPI+"/groups"
        $AADGroup = Invoke-RestMethod -Method "POST" -Uri $GraphAPI -Body $params -Headers $authHeader
        WriteHost 2 ("AAD Group "+$AADGroupName+ " created successfully!")
        $AADGroupid = $AADGroup.id
        $GraphAPI = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$appId/assign"
        $JSON =
@"
    {
        "mobileAppAssignments": [
        {
            "@odata.type": "#microsoft.graph.mobileAppAssignment",
            "target": {
            "@odata.type": "#microsoft.graph.groupAssignmentTarget",
            "groupId": "$AADGroupid"
            },
            "intent": "required"
        }
        ]
    }
"@

        WriteHost 1 "Create Group assignment..."
        $JSON
        Invoke-RestMethod -Method "POST" -Uri $GraphAPI -Body $JSON -Headers $authHeader
        WriteHost 2 "Assignment successful"
    }
    #---------------------------------------------------------[Add IntuneWin to Azure Table]--------------------------------------------------------
    function AddAzureTable {
        $global:SourceBody = @{
            'RowKey'=($global:appId)
            'PartitionKey'="IntuneWin"
            'Name'=($Global:XMLname)
            'Version'=($Global:XMLversion)
            'Language'=($Global:XMLlanguage)
            'Customer'=($Global:XMLprefix)
            'Vendor'=($Global:XMLvendor)
            'Revision'=($Global:XMLRevision)
            'Scriptversion'=($Global:XMLscriptversion)
            'UploadUser'= $Global:IntuneToken.ResultObject.Account.Username
            'SoftwareType'=($Global:XMLsoftwaretype)
            'IntuneWinURI'=($global:sasUriClean)
            'EncryptionKey'=($global:fileEncryptionInfo.fileEncryptionInfo.encryptionKey)
            'InitializationVector'=($global:fileEncryptionInfo.fileEncryptionInfo.initializationVector)
            }

        $global:sourceHeader = @{
            'Content-Type' = 'application/json'
            'Accept'="*/*"
            }

        $StorageURL = "https://widstwaas001.table.core.windows.net/IntuneUploadTable"+($configPS.SASToken)
        Invoke-RestMethod -Method "POST" -Uri $StorageURL -Body ($global:SourceBody|ConvertTo-Json) -Header $global:sourceHeader | Out-Null
    }


    # ################################################################################
    # -------------------------------- [Start Script] --------------------------------
    # --------------------------------------------------------------------------------

    $PSScriptRoot | Out-Null

    while ( !$SourcePath ) { $SourcePath = Read-Host -Prompt 'Please enter the Source Path' }
    while ( $SourcePath.EndsWith("\") ) { $SourcePath = $SourcePath -replace '.$' }


    try {
        Write-Host -f Magenta "[Pending] -> Check for updates..."
        $version = (Get-Module -ListAvailable IntuneWinPackage) | Sort-Object Version -Descending  | Select-Object Version -First 1
        $psgalleryversion = Find-Module -Name IntuneWinPackage | Sort-Object Version -Descending | Select-Object Version -First 1
        $stringver = $version | Select-Object @{n='ModuleVersion'; e={$_.Version -as [string]}}
        $a = $stringver | Select-Object Moduleversion -ExpandProperty Moduleversion
        $onlinever = $psgalleryversion | Select-Object @{n='OnlineVersion'; e={$_.Version -as [string]}}
        $b = $onlinever | Select-Object OnlineVersion -ExpandProperty OnlineVersion
        if ([version]"$a" -ge [version]"$b") {
            Write-Host -f DarkGreen "`r[Done] -> Check for updates "
        }
        else {
        Write-Host -f Yellow "`r[Warning] -> This PowerShell module is out of date! Please run Update-Module IntuneWinPackage in a PowerShell session with elevated rights."
        break
        }
    }
    catch {
        Write-Host -f Red "`r[Error] -> There is an error with the PowerShell Module. Is it even installed? Try 'Install-Module IntuneWinPackage' in a PowerShell session with elevated rights."
        break
    }


    if (!$SourcePath){
        $SourcePath = Read-Host "Source Path: "
    }
    WriteHost 1 "Checking parameters..."


    if (![System.IO.File]::Exists("$SourcePath\Application.png")){
        WriteHost 3 "No Icon (Application.png)"
        Read-Host -Prompt "Press any key to continue"
        exit
    }
    $LocalAppdata = $env:LOCALAPPDATA
    $temp = "$LocalAppdata\temp"
    Write-Host -f Magenta "[Pending] -> Checking config file"
    if (!(Test-Path -Path "$LocalAppdata\IntuneWinPackage\config.json")){
      New-Item -ItemType Directory -Force -Path "$LocalAppdata\IntuneWinPackage" | Out-Null
      New-Item -Path "$LocalAppdata\IntuneWinPackage" -Name "config.json" -Value '{"SASToken": ""}' -Force | Out-Null
    }

    #Config
    $configJSON = Get-Content -Path "$LocalAppdata\IntuneWinPackage\config.json" -raw
    $configPS = ConvertFrom-Json $configJSON

    if(!$configPS.SASToken){
      Write-Host -f Yellow "`r[Warning] -> No SAS Token set! "
      $configPS.SASToken = Read-Host "SAS Token: "
      $configJSON = ConvertTo-Json -depth 32 $configPS
      $configJSON | Out-File "$LocalAppdata\IntuneWinPackage\config.json"
    }
    Write-Host -f DarkGreen "`r[Done] -> Checking config file "

    WriteHost 2 "Parameters OK!"

    $baseUrl = "https://graph.microsoft.com/beta/deviceAppManagement/"

    $logRequestUris = $true;
    $logHeaders = $false;
    $logContent = $true;


    $azureStorageUploadChunkSizeInMb = 6l;

    Test-AuthToken
    Get-XMLFile
    New-IntuneWinApp

    $SourceFile = "$temp\$XMLname\Deploy-Application.intunewin"
    $DetectionXML = Get-IntuneWinXML "$SourceFile" -fileName "detection.xml"
    $RegistryRule = New-DetectionRule -Registry -RegistryKeyPath "$Global:SoftwareRegKey" -RegistryValue "Status" -RegistryDetectionType string -check32BitRegOn64System False
    $DetectionRule = @($RegistryRule)
    $ReturnCodes = Get-DefaultReturnCodes
    $ReturnCodes += New-ReturnCode -returnCode 302 -type softReboot
    $ReturnCodes += New-ReturnCode -returnCode 145 -type hardReboot

    if ($ServiceUI){
        Add-Win32Lob -SourceFile "$SourceFile" -publisher "$Global:XMLvendor" `
        -description "Description" -detectionRules $DetectionRule -returnCodes $ReturnCodes `
        -installCmdLine "ServiceUI.exe -Process:explorer.exe Deploy-Application.exe -DeploymentType Install" `
        -uninstallCmdLine "ServiceUI.exe -Process:explorer.exe Deploy-Application.exe -DeploymentType Uninstall"
    }else {
        Add-Win32Lob -SourceFile "$SourceFile" -publisher "$Global:XMLvendor" `
        -description "Description" -detectionRules $DetectionRule -returnCodes $ReturnCodes `
        -installCmdLine "Deploy-Application.exe -DeploymentType Install" `
        -uninstallCmdLine "Deploy-Application.exe -DeploymentType Uninstall"
    }

    if ($CreateGroup){
        if (Get-AADGroup) {
            WriteHost 4 "There already exists a group named $AADGroupName. Should a new group be created?[y/n] (default: n)"
            $Answer = Read-Host
            if ($Answer -eq "y") {
                Add-AADGroup
            }
        }
        else {
            Add-AADGroup
        }
    }

    AddAzureTable

    Remove-Item -LiteralPath "$temp\$XMLname" -Force -Recurse
}