microsoft / AL

Home of the Dynamics 365 Business Central AL Language extension for Visual Studio Code. Used to track issues regarding the latest version of the AL compiler and developer tools available in the Visual Studio Code Marketplace or as part of the AL Developer Preview builds for Dynamics 365 Business Central.
MIT License
745 stars 244 forks source link

AL:Publish in powershell? #2734

Open TimothyWatkins opened 6 years ago

TimothyWatkins commented 6 years ago

Is there a method of publishing a compiled nav app file to a remote host? VSCode does it so neatly with AL:Publish which uses a variety of parameters from the launch.json file.

"name": "My App", "server": "https://mydevelopment.net", "serverInstance": "NAV", "tenant": "default", "authentication": "UserPassword", "port": 7149

Using the alc.exe is a good tool to compile AL code locally but I'm missing a method of publishing and installing the compiled app remotely to the server. Specifically, this could allow an automatic build and release process in VSTS to dev and test environments...

Any ideas would be appreciated.

thpeder commented 6 years ago

Hi @TimothyWatkins, The only other option we have right now for publishing extensions is by using the Publish-NavApp Powershell command as part of the management module. https://docs.microsoft.com/en-us/powershell/module/microsoft.dynamics.nav.apps.management/publish-navapp?view=dynamicsnav-ps-2018

TimothyWatkins commented 6 years ago

Yes, correct, but not to a remote server right?

Sent from my iPhone

On 27 Jun 2018, at 00:13, Thomas Pedersen notifications@github.com<mailto:notifications@github.com> wrote:

Hi @TimothyWatkinshttps://github.com/TimothyWatkins, The only other option we have right now for publishing extensions is by using the Publish-NavApp Powershell command as part of the management module. https://docs.microsoft.com/en-us/powershell/module/microsoft.dynamics.nav.apps.management/publish-navapp?view=dynamicsnav-ps-2018

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/Microsoft/AL/issues/2734#issuecomment-400323889, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ASyWAjcJnpJuIj5bkHVTqXR1XVA0oM2mks5uAkF1gaJpZM4U39V_.

DennisReinecke commented 6 years ago

You can. Create a powershell session to the remote server. Copy the File to the remote server. Invoke your publish script on remote server session

$PSServerSession = New-PSSession $RemoteServer Copy-Item -ToSession $PSServerSession -Path $AppFilePath -Destination "$($ServerFilePath)"

Invoke-Command -Session $PSServerSession -ScriptBlock { Import-Module 'C:\Program Files (x86)\Microsoft Dynamics NAV\110\RoleTailored Client\Microsoft.Dynamics.Nav.Model.Tools.psd1' -WarningAction SilentlyContinue | out-null Import-Module 'C:\Program Files\Microsoft Dynamics NAV\110\Service\NavAdminTool.ps1' -WarningAction SilentlyContinue | out-null Import-Module 'C:\Program Files (x86)\Microsoft Dynamics NAV\110\RoleTailored Client\Microsoft.Dynamics.Nav.Apps.Tools.psd1' -WarningAction SilentlyContinue | out-null Publish-NAVApp -ServerInstance $($Using:ServerInstance) -Path "$($Using:ServerFilePath)" -SkipVerification }

marknitek commented 5 years ago

You can use the WebDeploy.exe that sits in the same directory as the alc compiler (vs code extension directory), it requires dev port of course.

%userprofile%\.vscode\extensions\ms-dynamics-smb.al-2.1.69331\bin\webdeploy.exe

Here part of some c# code for a standalone installer that uses webdeploy.exe:

            var argsBuilder = new List<string>();
            argsBuilder.Add("publish");
            argsBuilder.Add($"--Directory \"{workingDir}\"");
            argsBuilder.Add($"--PackageFileName \"{packageFile}\"");
            argsBuilder.Add($"--Authentication {Authentication}");
            argsBuilder.Add($"--Server {Server}");
            argsBuilder.Add($"--ServerInstance {Instance}");           
            var args = string.Join(" ", argsBuilder);
            var startInfo = new ProcessStartInfo("webdeploy.exe", args);

Note: Authentication with Windows is easy, but UserPassword ist not... But it can be done

JulianTillmann commented 5 years ago

@DennisReinecke: you would Need to be admin on the remote machine though, wouldn't you?

DennisReinecke commented 5 years ago

Sorry I dont know. I'm an Inhouse-Dev and I got Admin rights on our Server.

You dont have to provide admin rights to every developer. you can run the Powershell Session with different Credentials or with SSH. Check New-PSSession Docu

mattiasboustedt commented 5 years ago

Have a look at this script:

https://github.com/microsoft/navcontainerhelper/blob/master/AppHandling/Publish-NavContainerApp.ps1

Look at the variable "useDevEndpoint", that part of the code can easily be extracted for your purpose and used outside of docker. Though, it does not handle an upgrade of an app, so it would require you to use "SchemaUpdateMode=recreate", eg not suitable for production environments. If you find a way to do an upgrade, please tell me.

marknitek commented 5 years ago

As far as i know the only way to do CD in Production (or anywhere with real data upgrades) is by using the regular PS commands at least that is what we do in our pipeline (open a pssession on the service tier and execute the required commands)

To use the dev endpoint for this would be a lot easier and more robust...

atoader commented 5 years ago

@TimothyWatkins @DennisReinecke @marknitek @mattiasboustedt what advantages do you see to using the DEV endpoint for publishing extensions vs using the management commandlets?

marknitek commented 5 years ago

@atoader The powershell cmdlets only work if you are on the same machine as the service tier. If i want to do deployment with an Azure DevOps Agent i need to open a pssession to the service tier machine and invoke the cmdlets there. This is not a great Solution. In fact in our scenario we use docker containers and therefore need to open another pssession to the container to invoke the cmdlets. (=> pssession in a pssession) This is a lot of extra work just for a simple publish/install task.

If we could use the dev endpoints for that it would be a simple http call that works from anywhere and also from a machine without powershell access (lets say a non windows machine for example).

There is also a security impact if you need access to the service tier machine. It is common practice to not allow access to these machines for regular developers. If we can use the endpoint for that, no access rights to the machine would be required to make deployments.

DennisReinecke commented 5 years ago

I dont know if this will help our development and deployments - but we are inhouse and have full access on all our servers. I was playing around with all this powershell possibilities alot. Now we only need 1 powershell function to:

if i cannot handle all this steps via DEV endpoint, i dont see any advantages and i will stick to powershell.

mattiasboustedt commented 5 years ago

I've posted an idea to Microsoft regarding this (more or less, at least), though it seems like it is currently not in their roadmap.

https://experience.dynamics.com/ideas/idea/?ideaid=58a25895-668b-e911-80e7-0003ff68e84c

atoader commented 5 years ago

@mattiasboustedt upgrade is automatically ran if the extension you publish has a higher version than the extension installed on the server. This feature is available in the latest development preview. Please try it out and let us know if you encounter any problems by opening an issue here.

mattiasboustedt commented 5 years ago

That's great news @atoader, thank you for the information. I will definitely try it out!

vremeni4 commented 4 years ago

I really do not understand why do we need to have access to the Service tier, have to use Power-shell and be admin to publish an app on prem. On cloud there is a page 2507 "Upload And Deploy Extension" which is used for remote deployment. Why can't we use the same thing onprem ?

One of our customer does not want developers to have access on the service tier, because of security reasons e.g. financial data exchanged via Service Tier. Currently we are facing a misery every time when we need to update the extension. As we need to send the app to the IT manager so he can publish it.

vytjak commented 4 years ago

I agree with most of the points above. if VS Code can publish an app to a remote server (via dev endpoint), why isn't there a way to do it from the command line?

mattiasboustedt commented 4 years ago

@mattiasboustedt upgrade is automatically ran if the extension you publish has a higher version than the extension installed on the server. This feature is available in the latest development preview. Please try it out and let us know if you encounter any problems by opening an issue here.

Haven't had a chance to try this until now, works great so far. Will do some further and more thorough testing.

Here's a one-liner for those of you who would like to try (tried on a UNIX system, but the principle is the same):

curl -u username:password -X POST -F file='@yourApp.app' http://containerName:7049/DevServerInstance/dev/apps?SchemaUpdateMode=synchronize -v

michvllni commented 3 years ago

I've used freddies aproach in the Publish-AppInBcContainer Script and put that into a function:

function Publish-AppToDevEndpoint {
    param(
        [Parameter(Mandatory = $true)]
        [ValidateScript( { Test-Path "$_" })]
        [String]
        $appFile,
        [Parameter(Mandatory = $true)]
        [PSCredential]
        $credential,
        [Parameter(Mandatory=$true)]
        [ValidatePattern('http[s]?://.*')]
        [String]
        $devEndpointUri,
        [Parameter(Mandatory=$true)]
        [int]
        $devPort,
        [Parameter(Mandatory=$true)]
        [string]
        $instanceName
    )
    Add-Type -AssemblyName System.Net.Http
    $handler = New-Object System.Net.Http.HttpClientHandler
    $HttpClient = [System.Net.Http.HttpClient]::new($handler)
    $pair = ("$($Credential.UserName):" + [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)))
    $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
    $base64 = [System.Convert]::ToBase64String($bytes)
    $HttpClient.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Basic", $base64);
    $HttpClient.Timeout = [System.Threading.Timeout]::InfiniteTimeSpan
    $HttpClient.DefaultRequestHeaders.ExpectContinue = $false

    $url = "$($devEndpointUri):$port/$instanceName/dev/apps?SchemaUpdateMode=forcesync"

    $appName = [System.IO.Path]::GetFileName("$appFile")

    $multipartContent = [System.Net.Http.MultipartFormDataContent]::new()
    $FileStream = [System.IO.FileStream]::new("$appFile", [System.IO.FileMode]::Open)
    try {
        $fileHeader = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("form-data")
        $fileHeader.Name = "$AppName"
        $fileHeader.FileName = "$appName"
        $fileHeader.FileNameStar = "$appName"
        $fileContent = [System.Net.Http.StreamContent]::new($FileStream)
        $fileContent.Headers.ContentDisposition = $fileHeader
        $multipartContent.Add($fileContent)
        Write-Host "Publishing $appName to $url"
        $result = $HttpClient.PostAsync($url, $multipartContent).GetAwaiter().GetResult()
        if (!$result.IsSuccessStatusCode) {
            $message = "Status Code $($result.StatusCode) : $($result.ReasonPhrase)"
            try {
                $resultMsg = $result.Content.ReadAsStringAsync().Result
                try {
                    $json = $resultMsg | ConvertFrom-Json
                    $message += "`n$($json.Message)"
                }
                catch {
                    $message += "`n$resultMsg"
                }
            }
            catch {}
            throw $message
        }
        else {
            Write-Host "Success: $appName was published and installed"
        }
    }
    finally {
        $FileStream.Close()
    }
}

Usage:

$cedential = New-Object System.Management.Automation.PSCredential ("navaccount", ("navaccount" | ConvertTo-SecureString -AsPlainText -Force))
Publish-AppToDevEndPoint -appFile "Path\to\my.app" -credential $credential -devEndpointUri "http:\\localhost" -devPort 7049 -instanceName BC

NOTE:

jsaibron commented 3 years ago

You can use the WebDeploy.exe that sits in the same directory as the alc compiler (vs code extension directory), it requires dev port of course.

%userprofile%\.vscode\extensions\ms-dynamics-smb.al-2.1.69331\bin\webdeploy.exe

Here part of some c# code for a standalone installer that uses webdeploy.exe:

            var argsBuilder = new List<string>();
            argsBuilder.Add("publish");
            argsBuilder.Add($"--Directory \"{workingDir}\"");
            argsBuilder.Add($"--PackageFileName \"{packageFile}\"");
            argsBuilder.Add($"--Authentication {Authentication}");
            argsBuilder.Add($"--Server {Server}");
            argsBuilder.Add($"--ServerInstance {Instance}");           
            var args = string.Join(" ", argsBuilder);
            var startInfo = new ProcessStartInfo("webdeploy.exe", args);

Note: Authentication with Windows is easy, but UserPassword ist not... But it can be done

Hi, How use userpassword with webdeploy ? I saw a userpasswordcache.dat but how generate it ? Thanks