├── .gitignore ├── images ├── icon.png └── runasadmin.png ├── .vscodeignore ├── .vscode ├── tasks.json ├── settings.json └── launch.json ├── src ├── powershell │ ├── updategitmodified.ps1 │ ├── updategitdelta.ps1 │ ├── modules │ │ ├── commonFunctions.psm1 │ │ ├── commonDialogs.psm1 │ │ ├── module.psm1 │ │ ├── version.psm1 │ │ ├── compile_objects.psm1 │ │ ├── copy_db.psm1 │ │ ├── service_administration.psm1 │ │ ├── import_objects.psm1 │ │ ├── export_objects.psm1 │ │ └── new_nav.psm1 │ ├── createNewEnvironment.ps1 │ ├── start_rtc.ps1 │ ├── remove_remote.ps1 │ ├── start_remote.ps1 │ ├── new_remote.ps1 │ ├── start_shell.ps1 │ ├── compile_obj.ps1 │ ├── copy_db.ps1 │ ├── nav2git.ps1 │ ├── git2nav.ps1 │ ├── start_ide.ps1 │ ├── remove_env.ps1 │ ├── shell.ps1 │ └── new_env.ps1 ├── shared │ └── helpers.ts ├── newenv.ts ├── folders.ts ├── modules.ts ├── scripts.ts ├── extension.ts ├── exportselection.ts ├── logging.ts ├── newversion.ts ├── powershell.ts ├── actions.ts └── settings.ts ├── CHANGELOG.md ├── tsconfig.json ├── gulpfile.js ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | out 3 | node_modules 4 | *.vsix 5 | npm-debug.log -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudReadySoftware/DynamicsNAVSCM/HEAD/images/icon.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | src/** 3 | **/*.map 4 | .gitignore 5 | tsconfig.json 6 | *.vsix 7 | gulpfile.js -------------------------------------------------------------------------------- /images/runasadmin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CloudReadySoftware/DynamicsNAVSCM/HEAD/images/runasadmin.png -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "command": "npm", 4 | "isShellCommand": true, 5 | "showOutput": "silent", 6 | "args": ["run", "build"], 7 | "isBackground": true, 8 | "problemMatcher": "$tsc-watch" 9 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "out": true, 4 | "node_modules": true, 5 | "*.vsix": true 6 | }, 7 | "search.exclude": { 8 | "out": true, 9 | "*.vsix": true 10 | } 11 | } -------------------------------------------------------------------------------- /src/powershell/updategitmodified.ps1: -------------------------------------------------------------------------------- 1 | #Update Modified based on Original and Delta 2 | Remove-Item (join-path $ModifiedDestination '*.TXT') 3 | Update-NAVApplicationObject -TargetPath $SourceDestination -DeltaPath $DeltaDesitination -ResultPath $ModifiedDestination -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to the "nav-developer-extensions" extension will be documented in this file. 3 | 4 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 5 | 6 | ## [Unreleased] 7 | - Initial release -------------------------------------------------------------------------------- /src/powershell/updategitdelta.ps1: -------------------------------------------------------------------------------- 1 | #Update the Delta folder based on Original and Modified 2 | Remove-Item (join-path $DeltaDestination '*.DELTA') 3 | Compare-NAVApplicationObject -OriginalPath $SourceDestination -ModifiedPath $ModifiedDestination -DeltaPath $DeltaDesitination -ErrorAction Stop -------------------------------------------------------------------------------- /src/shared/helpers.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | export default class Helpers 4 | { 5 | public static throwError(error: string) { 6 | window.showErrorMessage(error); 7 | } 8 | public static showInformation(message: string) { 9 | window.showInformationMessage(message); 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/powershell/modules/commonFunctions.psm1: -------------------------------------------------------------------------------- 1 | function Get-TempFolder 2 | { 3 | [CmdletBinding()] 4 | Param( 5 | [Parameter()] 6 | [String]$SourceFolder = $env:temp 7 | ) 8 | Process 9 | { 10 | $Foldername = Join-Path $SourceFolder (New-Guid) 11 | New-Item -Path $Foldername -ItemType Directory 12 | } 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2017", 5 | "outDir": "out", 6 | "lib": [ 7 | "es2017" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /src/powershell/createNewEnvironment.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory)] 3 | [String[]]$Modules, 4 | [Parameter(Mandatory)] 5 | [string]$InitialDirectory 6 | ) 7 | 8 | foreach($Module in $Modules) 9 | { 10 | Import-Module $Module -ErrorAction Stop 11 | Set-Location $env:SystemDrive 12 | } 13 | 14 | OpenFileDialog -Title "Select ZIP Build file" -InitialDirectory $InitialDirectory -Filter "Zipfile (*.zip)|*.zip" 15 | -------------------------------------------------------------------------------- /src/powershell/start_rtc.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter()] 3 | [ValidateScript({Test-Path -Path $PSItem -PathType Container})] 4 | [String]$Repo 5 | ) 6 | 7 | Import-Module (Join-Path $PSScriptRoot "module.psm1") 8 | 9 | Set-GitNAVSettings -LocalRepo $Repo 10 | 11 | Test-NAVFile $NAVRTC 12 | 13 | $ArgumentsList = "DynamicsNAV://{0}/{1}//" -f $env:computername, $NAVServiceInstance 14 | Start-Process $NAVRTC -ArgumentList $ArgumentsList -------------------------------------------------------------------------------- /src/powershell/remove_remote.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter()] 3 | [ValidateScript({Test-Path -Path $PSItem -PathType Container})] 4 | [String]$Repo 5 | ) 6 | 7 | Import-Module (Join-Path $PSScriptRoot "module.psm1") 8 | 9 | Set-GitNAVRemoteSettings -LocalRepo $Repo 10 | 11 | if(Test-Path $global -PathType Container) 12 | { 13 | Get-ChildItem -Path $global -Recurse -Force | Remove-Item -Force -Recurse 14 | Remove-Item $BaseFolder -Force -Recurse 15 | } -------------------------------------------------------------------------------- /src/powershell/start_remote.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter()] 3 | [ValidateScript({Test-Path -Path $PSItem -PathType Container})] 4 | [String]$Repo 5 | ) 6 | 7 | Import-Module (Join-Path $PSScriptRoot "module.psm1") 8 | 9 | Set-GitNAVRemoteSettings -LocalRepo $Repo 10 | 11 | Test-NAVFile $NAVIde 12 | $Arguments = "servername={0}, database={1}, ntauthentication=1, id={1}" -f $RemoteDBInstance,$RemoteDBName 13 | Start-Process -FilePath $NAVIde -ArgumentList $Arguments -------------------------------------------------------------------------------- /src/powershell/new_remote.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter()] 3 | [ValidateScript({Test-Path -Path $PSItem -PathType Container})] 4 | [String]$Repo 5 | ) 6 | 7 | Import-Module (Join-Path $PSScriptRoot "module.psm1") 8 | 9 | Set-GitNAVRemoteSettings -LocalRepo $Repo 10 | 11 | $RTCFolderMissing = -not (Test-Path -Path $RTCFolder -PathType Container) 12 | if($RTCFolderMissing) 13 | { 14 | Expand-NAVArchive -RoleTailoredClient $RTCFolderMissing 15 | } 16 | 17 | Install-NAVAddIns -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 10 | "stopOnEntry": false, 11 | "sourceMaps": true, 12 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], 13 | "preLaunchTask": "npm" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/newenv.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | export function createFolderIfNotExist(Path: string){ 5 | console.log(Path); 6 | if(!fs.existsSync(Path)) { 7 | fs.mkdirSync(Path); 8 | } 9 | } 10 | 11 | export function createGitIgnorefile(Workspacefolder: string) { 12 | let gitignorefile = path.join(Workspacefolder, '.gitignore'); 13 | if(fs.existsSync(gitignorefile)){ 14 | return 15 | } 16 | var gitignorecontent = `lastimportedgithash\ntemp`; 17 | fs.writeFile(gitignorefile, gitignorecontent); 18 | } -------------------------------------------------------------------------------- /src/powershell/start_shell.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | 3 | param( 4 | [Parameter(Mandatory)] 5 | [String[]]$Variables, 6 | [Parameter(Mandatory)] 7 | [String[]]$Modules 8 | ) 9 | 10 | $shellfile = Join-Path $PSScriptRoot "shell.ps1" 11 | $modulesParameter = '-Modules "{0}"' -f ($Modules -join ';') 12 | $VariablesParameter = '-Variables "{0}"' -f ($Variables -join ';') 13 | $FileParameter = '-File "{0}"' -f $shellfile 14 | Start-Process powershell -ArgumentList "-NoProfile","-NoExit","-ExecutionPolicy ByPass",$FileParameter,$modulesParameter,$VariablesParameter -Verb RunAs -------------------------------------------------------------------------------- /src/folders.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { join } from 'path'; 3 | 4 | 5 | export const WORKSPACE = workspace.rootPath; 6 | export const ADDIN = WORKSPACE ? join(WORKSPACE, 'addin/') : null; 7 | export const REPORTLAYOUTS = WORKSPACE ? join(WORKSPACE, 'reportlayouts/') : null; 8 | export const ADDINRESOURCES = WORKSPACE ? join(WORKSPACE, 'addinresources/') : null; 9 | export const SOURCE = WORKSPACE ? join(WORKSPACE, 'src/') : null; 10 | export const LASTIMPORTEDGITHASH = WORKSPACE ? join(WORKSPACE, 'lastimportedgithash') : null; 11 | export const TEMP = WORKSPACE ? join(WORKSPACE, 'temp/') : null; -------------------------------------------------------------------------------- /src/powershell/compile_obj.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory)] 3 | [String[]]$Modules, 4 | [Parameter(Mandatory)] 5 | [String]$NAVIDE, # Must be called NAVIDE for modules sake 6 | [Parameter(Mandatory)] 7 | [String]$DatabaseName, 8 | [Parameter()] 9 | [Switch]$Foreground 10 | ) 11 | 12 | foreach($module in $Modules) 13 | { 14 | Import-Module $module -DisableNameChecking 15 | Set-Location $env:SystemDrive 16 | } 17 | 18 | if(!(Test-Path $NAVIDE)) { 19 | throw "Cannot find NAV at '$NAVIDE'" 20 | } 21 | $Background = -not $Foreground 22 | $Background = $false 23 | if($Background) 24 | { 25 | Write-Host "Compiling in the background" 26 | } 27 | 28 | Start-IDECompileNAVObject -Modules $Modules -NAVIDE $NAVIDE -DatabaseName $DatabaseName -Background:$Background -------------------------------------------------------------------------------- /src/powershell/copy_db.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter()] 3 | [String[]]$Modules, 4 | [Parameter()] 5 | [String]$SourceDatabaseInstance, 6 | [Parameter()] 7 | [String]$SourceDatabaseName, 8 | [Parameter()] 9 | [String]$CommonSQLLocation, 10 | [Parameter()] 11 | [String]$DestinationDatabaseName 12 | ) 13 | 14 | foreach($Module in $Modules) 15 | { 16 | Import-Module $Module -ErrorAction Stop 17 | Set-Location $env:SystemDrive 18 | } 19 | 20 | Copy-NAVDatabase -SourceDatabaseInstance $SourceDatabaseInstance -SourceDatabaseName $SourceDatabaseName -CommonSQLLocation $CommonSQLLocation -DestinationDatabaseName $DestinationDatabaseName 21 | Invoke-SQLCmd -Query "EXEC sp_addrolemember 'db_owner', 'NT AUTHORITY\System'" -Database $DestinationDatabaseName -ServerInstance "." -------------------------------------------------------------------------------- /src/powershell/nav2git.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory)] 3 | [String[]]$Modules, 4 | [Parameter(Mandatory)] 5 | [String]$DatabaseName, 6 | [Parameter(Mandatory)] 7 | [String]$NAVIDE, 8 | [Parameter(Mandatory)] 9 | [String]$DestinationFolder, 10 | [Parameter()] 11 | [String]$TempFolder, 12 | [Parameter()] 13 | [String[]]$Filters, 14 | [Parameter()] 15 | [Boolean]$DateModification 16 | ) 17 | 18 | foreach($Module in $Modules) 19 | { 20 | Import-Module $Module -DisableNameChecking 21 | Set-Location $env:SystemDrive 22 | } 23 | if(-not (Test-Path $NAVIDE)) 24 | { 25 | throw "Cant find NAVIDE at $NAVIDE" 26 | } 27 | 28 | Export-IDENAVObject -DatabaseName $DatabaseName -DestinationFolder $DestinationFolder -TempFolder $TempFolder -Filters $Filters -DateModification $DateModification -------------------------------------------------------------------------------- /src/powershell/git2nav.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory)] 3 | [String[]]$Modules, 4 | [Parameter(Mandatory)] 5 | [String]$ObjectsFolder, 6 | [Parameter(Mandatory)] 7 | [String]$WorkspaceFolder, 8 | [Parameter(Mandatory)] 9 | [String]$SolutionName, 10 | [Parameter(Mandatory)] 11 | [String]$DatabaseName, 12 | [Parameter(Mandatory)] 13 | [String]$LastImportGitHashFilepath 14 | ) 15 | 16 | foreach($Module in $Modules) 17 | { 18 | Import-Module $Module -ErrorAction Stop -DisableNameChecking 19 | Set-Location $env:SystemDrive 20 | } 21 | if(-not (Test-Path $NAVIDE)) 22 | { 23 | throw "Cant find NAVIDE at $NAVIDE" 24 | } 25 | 26 | Import-IDENAVObject -ObjectsFolder $ObjectsFolder -Workspacefolder $WorkspaceFolder -SolutionName $SolutionName -DatabaseName $DatabaseName -LastImportGitHashFilepath $LastImportGitHashFilepath -------------------------------------------------------------------------------- /src/modules.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { POWERSHELLPATH } from './scripts'; 3 | 4 | const MODULESPATH = join(POWERSHELLPATH, 'modules'); 5 | export const COMPILE_OBJECTS = join(MODULESPATH, 'compile_objects.psm1'); 6 | export const COPY_DB = join(MODULESPATH, 'copy_db.psm1'); 7 | export const EXPORT_OBJECTS = join(MODULESPATH, 'export_objects.psm1'); 8 | export const IMPORT_OBJECTS = join(MODULESPATH, 'import_objects.psm1'); 9 | export const NEW_NAV = join(MODULESPATH, 'new_nav.psm1'); 10 | export const SERVICE_ADMINISTRATION = join(MODULESPATH, 'service_administration.psm1'); 11 | export const COMMON_DIALOGS = join(MODULESPATH, 'commonDialogs.psm1'); 12 | export const VERSION = join(MODULESPATH, 'version.psm1'); 13 | export const COMMONFUNCTIONS = join(MODULESPATH, 'commonFunctions.psm1'); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var del = require('del'); 3 | var ts = require("gulp-typescript"); 4 | var sourcemaps = require('gulp-sourcemaps'); 5 | 6 | gulp.task('clean', function() { 7 | return del('build/**/*') 8 | }); 9 | 10 | gulp.task('compile', ['clean'], function () { 11 | return gulp.src('src/*.ts') 12 | .pipe(sourcemaps.init()) 13 | .pipe(ts({ 14 | module: "commonjs", 15 | target: "es6", 16 | lib: [ 17 | "es6" 18 | ], 19 | sourceMap: true, 20 | alwaysStrict: true 21 | })) 22 | .pipe(sourcemaps.write()) 23 | .pipe(gulp.dest('build/')); 24 | }); 25 | 26 | gulp.task('copy', ['clean'], function() { 27 | return gulp.src('src/powershell/**/*').pipe(gulp.dest('build/powershell/')); 28 | }); 29 | 30 | gulp.task('default', ['clean', 'copy', 'compile']); -------------------------------------------------------------------------------- /src/powershell/start_ide.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | [Parameter(Mandatory)] 3 | [String]$NAVIDE, 4 | [Parameter(Mandatory)] 5 | [String]$DatabaseName 6 | ) 7 | 8 | if(-not (Test-Path $NAVIde)) { 9 | throw "NAV IDE not found. '$NAVIde'" 10 | } 11 | 12 | Add-Type @" 13 | using System; 14 | using System.Runtime.InteropServices; 15 | public class Tricks { 16 | [DllImport("user32.dll")] 17 | [return: MarshalAs(UnmanagedType.Bool)] 18 | public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); 19 | } 20 | "@ -Debug:$false 21 | 22 | $process = Get-Process | Where-Object {$PSItem.Path -eq $NAVIDE} 23 | if($process) { 24 | [void] [Tricks]::ShowWindowAsync($process.MainWindowHandle, 2) 25 | [void] [Tricks]::ShowWindowAsync($process.MainWindowHandle, 10) 26 | return 27 | } 28 | 29 | $Arguments = "servername={0}, database={1}, ntauthentication=1, ID={1}" -f $env:computername,$DatabaseName 30 | Start-Process -FilePath $NAVIde -ArgumentList $Arguments -------------------------------------------------------------------------------- /src/powershell/remove_env.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | 3 | Param( 4 | [Parameter(Mandatory)] 5 | [String]$ServiceInstanceName, 6 | [Parameter(Mandatory)] 7 | [String]$BaseFolder 8 | ) 9 | Write-Host $ServiceInstanceName 10 | $ServiceNameFilter = 'Name = "{0}"' -f $ServiceInstanceName 11 | Write-Host $ServiceNameFilter 12 | $service = Get-WmiObject win32_service -Filter $ServiceNameFilter 13 | $MultipleServices = $false 14 | if($service) 15 | { 16 | $MultipleServices = [bool](Get-Member -InputObject $service -Name 'Count' -MemberType Properties) 17 | } 18 | if ($MultipleServices) { 19 | throw "Not specific enough" 20 | } 21 | if ($service) { 22 | if ($service.State -eq 'Running') { 23 | Stop-Service -Name $service.Name -NoWait 24 | } 25 | $null = $service.Delete() 26 | Start-Sleep -Milliseconds 500 27 | } 28 | 29 | if(Test-Path $BaseFolder) 30 | { 31 | Remove-Item $BaseFolder -Force -Recurse -ErrorAction 'SilentlyContinue' 32 | Start-Sleep -Milliseconds 500 33 | if(Test-Path $BaseFolder) 34 | { 35 | Remove-Item $BaseFolder -Force -Recurse -ErrorAction 'SilentlyContinue' 36 | } 37 | } -------------------------------------------------------------------------------- /src/powershell/shell.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | 3 | param( 4 | [Parameter(Mandatory)] 5 | [String[]]$Variables, 6 | [Parameter(Mandatory)] 7 | [String[]]$Modules 8 | ) 9 | 10 | $NewVariables = $Variables -split ";" 11 | $NewModules = $Modules -split ";" 12 | $repository = $false 13 | $SolutionName = "" 14 | 15 | foreach($Module in $NewModules) 16 | { 17 | Import-Module $Module -ErrorAction Stop -DisableNameChecking 18 | Set-Location $env:SystemDrive 19 | } 20 | 21 | if($NewVariables.Count -lt 2) 22 | { 23 | throw "Not enough variables" 24 | } 25 | 26 | if($NewVariables.Count % 2 -ne 0) 27 | { 28 | throw "Uneven variable length" 29 | } 30 | 31 | $VariablesTable = @{} 32 | 33 | for($i = 0; $i -lt $NewVariables.Count;$i += 2) 34 | { 35 | $Name = $NewVariables[$i] 36 | $Value = $NewVariables[$i + 1] 37 | $VariablesTable.Add($Name, $Value) 38 | Set-Variable -Name $Name -Value $Value 39 | } 40 | if($repository) { 41 | if(Test-Path $repository) 42 | { 43 | Push-Location $repository 44 | } 45 | } 46 | if($SolutionName) { 47 | Write-Host "Shell for '$SolutionName'" 48 | } 49 | Write-Host 'Content of $VariablesTable:' 50 | $VariablesTable 51 | -------------------------------------------------------------------------------- /src/powershell/modules/commonDialogs.psm1: -------------------------------------------------------------------------------- 1 | Function OpenFileDialog 2 | { 3 | param( 4 | [Parameter()] 5 | [String]$Title = "Open File", 6 | [Parameter()] 7 | [String]$InitialDirectory, 8 | [Parameter()] 9 | [String]$Filter = "All Files (*.*)|*.*" 10 | ) 11 | 12 | $OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog 13 | $OpenFileDialog.Title = $Title 14 | $OpenFileDialog.InitialDirectory = $InitialDirectory 15 | $OpenFileDialog.Filter = $Filter 16 | $null = $OpenFileDialog.ShowDialog() 17 | $OpenFileDialog.FileName 18 | } 19 | 20 | Function ConfirmDialog 21 | { 22 | param( 23 | [string]$title="Confirm", 24 | [string]$message="Are you sure?" 25 | ) 26 | 27 | $choiceYes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'Answer Yes.' 28 | $choiceNo = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'Answer No.' 29 | $options = [System.Management.Automation.Host.ChoiceDescription[]]($choiceYes, $choiceNo) 30 | $result = $host.ui.PromptForChoice($title, $message, $options, 1) 31 | 32 | switch ($result) 33 | { 34 | 0 35 | { 36 | Return $true 37 | } 38 | 39 | 1 40 | { 41 | Return $false 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/scripts.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | export const POWERSHELLPATH = join(__dirname, 'powershell'); 4 | export const COMPILE_OBJ = join(POWERSHELLPATH, 'compile_obj.ps1'); 5 | export const COPY_DB = join(POWERSHELLPATH, 'copy_db.ps1'); 6 | export const GIT2NAV = join(POWERSHELLPATH, 'git2nav.ps1'); 7 | export const NAV2GIT = join(POWERSHELLPATH, 'nav2git.ps1'); 8 | export const UPDATEGITDELTA = join(POWERSHELLPATH, 'updategitdelta.ps1'); 9 | export const UPDATEGITMODIFIED = join(POWERSHELLPATH, 'updategitmodified.ps1'); 10 | export const NEW_ENV = join(POWERSHELLPATH, 'new_env.ps1'); 11 | export const NEW_REMOTE = join(POWERSHELLPATH, 'new_remote.ps1'); 12 | export const REMOVE_ENV = join(POWERSHELLPATH, 'remove_env.ps1'); 13 | export const REMOVE_REMOTE = join(POWERSHELLPATH, 'remove_remote.ps1'); 14 | export const START_IDE = join(POWERSHELLPATH, 'start_ide.ps1'); 15 | export const START_REMOTE = join(POWERSHELLPATH, 'start_remote.ps1'); 16 | export const START_RTC = join(POWERSHELLPATH, 'start_rtc.ps1'); 17 | export const START_SHELL = join(POWERSHELLPATH, 'start_shell.ps1'); 18 | export const HELLO = join(POWERSHELLPATH, 'hello.ps1'); 19 | export const NEW_ENVIRONMENT = join(POWERSHELLPATH, 'createNewEnvironment.ps1'); -------------------------------------------------------------------------------- /src/powershell/modules/module.psm1: -------------------------------------------------------------------------------- 1 | function Test-NAVFile 2 | { 3 | [CmdletBinding()] 4 | Param( 5 | [Parameter()] 6 | [String]$Path 7 | ) 8 | Process 9 | { 10 | if(-not (Test-Path $Path -Pathtype Leaf)) 11 | { 12 | throw ("Missing the file '{0}'" -f $Path) 13 | } 14 | } 15 | } 16 | 17 | function Complete-NAVVersioning 18 | { 19 | [CmdletBinding()] 20 | Param( 21 | ) 22 | Process 23 | { 24 | #NO EXPORT FIRST! IMPORTANT 25 | #Import-AllObjects 26 | #Compile all objects 27 | #Export solutionname 28 | $Filename = "{0}.fob" -f $NAVSolutionVersion 29 | $FilePath = Join-Path $NAVRepo $FileName 30 | if(Test-Path $FilePath) 31 | { 32 | Remove-Item $FilePath 33 | } 34 | Export-NAVApplicationObject -DatabaseName $NAVDatabaseName -Path $fobFile -Filter "Version List=@*$NAVSolutionName*" 35 | } 36 | } 37 | 38 | function Create-FolderIfNotExist 39 | { 40 | [CmdletBinding()] 41 | param ( 42 | [String]$MyFolder 43 | ) 44 | 45 | if ( -Not (Test-Path $MyFolder)) 46 | { 47 | New-Item $MyFolder -type directory 48 | } 49 | 50 | } 51 | 52 | function Remove-FolderIfExist 53 | { 54 | [CmdletBinding()] 55 | param ( 56 | [String]$MyFolder 57 | ) 58 | if (Test-Path $MyFolder) 59 | { 60 | Remove-Item $MyFolder -Recurse 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, commands } from 'vscode'; 2 | import * as customActions from './actions'; 3 | 4 | export function activate(context: ExtensionContext) { 5 | console.log('Extension "vscode-dynamics-nav" activated'); 6 | let commandList = [ 7 | commands.registerCommand('dynamicsnav.compile_obj', customActions.compileNAVObjects), 8 | commands.registerCommand('dynamicsnav.copy_db', customActions.copyDB), 9 | commands.registerCommand('dynamicsnav.git2nav', customActions.importObjects), 10 | commands.registerCommand('dynamicsnav.nav2git', customActions.pickFilesExport), 11 | commands.registerCommand('dynamicsnav.new_env', customActions.newEnvironment), 12 | commands.registerCommand('dynamicsnav.new_remote', null), 13 | commands.registerCommand('dynamicsnav.remove_env', customActions.removeEnvironment), 14 | commands.registerCommand('dynamicsnav.remove_remote', null), 15 | commands.registerCommand('dynamicsnav.start_ide', customActions.startIDE), 16 | commands.registerCommand('dynamicsnav.start_remote', null), 17 | commands.registerCommand('dynamicsnav.start_rtc', null), 18 | commands.registerCommand('dynamicsnav.start_shell', customActions.startShell), 19 | commands.registerCommand('dynamicsnav.initialize_workspace', customActions.init_workspace), 20 | commands.registerCommand('dynamicsnav.cleannavdatabase', null) 21 | ]; 22 | context.subscriptions.concat(commandList); 23 | } 24 | 25 | export function deactivate() { 26 | } 27 | -------------------------------------------------------------------------------- /src/exportselection.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickItem, QuickPickOptions, window } from 'vscode'; 2 | import { Settings } from './settings'; 3 | 4 | export class ExportOption implements QuickPickItem 5 | { 6 | description: string; 7 | detail?: string; 8 | label: string; 9 | key: string; 10 | } 11 | 12 | function getObjects() 13 | { 14 | let modified: ExportOption = { 15 | description: 'Export using custom fields', 16 | label: 'Custom filters', 17 | detail: `Export using the filters set in the setting "dynamicsnav.filters"`, 18 | key: "filters" 19 | } 20 | let all: ExportOption = { 21 | description: 'Export all the compiled objects.', 22 | detail: 'No filters. Slow, 2 min +', 23 | label: 'All', 24 | key: "all" 25 | } 26 | return [modified, all]; 27 | } 28 | 29 | export function selectItem(cb: Function) 30 | { 31 | let items = getObjects(); 32 | let options: QuickPickOptions = { 33 | matchOnDescription: false, 34 | placeHolder: "What objects do you want to export?" 35 | } 36 | let quickpick = window.showQuickPick(items, options); 37 | quickpick.then((item: ExportOption) => { 38 | if(item && cb){ 39 | let filters :boolean = item.key === "filters"; 40 | cb(filters); 41 | } 42 | }); 43 | } 44 | 45 | function getExportFilters(key: string, currentVersionNumber: string, nextVersionNumber: string) 46 | { 47 | switch(key) 48 | { 49 | case "all": 50 | return "Compiled=1"; 51 | case "solution": 52 | return [`Version List=@*${currentVersionNumber}*|@*${nextVersionNumber}*;Compiled=1`, `Modified=1;Compiled=1`] 53 | default: 54 | return ""; 55 | } 56 | } -------------------------------------------------------------------------------- /src/powershell/modules/version.psm1: -------------------------------------------------------------------------------- 1 | function Remove-NAVVersionNumber 2 | { 3 | [CmdletBinding()] 4 | Param( 5 | [Parameter(Mandatory)] 6 | [String]$VersionList, 7 | [Parameter(Mandatory)] 8 | [String]$VersionToRemove 9 | ) 10 | Process 11 | { 12 | $VersionArray = $VersionList -split "," 13 | [String[]]$NewVersionArray = foreach($Version in $VersionArray) { 14 | if(-not ($Version -match $VersionToRemove)) 15 | { 16 | $Version 17 | } 18 | } 19 | $NewVersionArray -join "," 20 | } 21 | } 22 | 23 | function Add-NAVVersionNumber 24 | { 25 | [CmdletBinding()] 26 | Param( 27 | [Parameter()] 28 | [String]$VersionList, 29 | [Parameter(Mandatory)] 30 | [String]$NewVersionNo 31 | ) 32 | Process 33 | { 34 | if($VersionList -eq "") 35 | { 36 | return $NewVersionNo 37 | } 38 | [String[]]$VersionArray = $VersionList -split "," 39 | $Added = $false 40 | $SolutionName = $NewVersionNo 41 | if($NewVersionNo -match "^\D+") 42 | { 43 | $SolutionName = $matches[0] 44 | } 45 | $NewVersionArray = New-Object "System.Collections.ArrayList" 46 | foreach($Version in $VersionArray) { 47 | if($Version -match $SolutionName) 48 | { 49 | $Version = $NewVersionNo 50 | $Added = $true 51 | } 52 | $null = $NewVersionArray.Add($Version) 53 | } 54 | if(-not $Added) 55 | { 56 | $null = $NewVersionArray.Add($NewVersionNo) 57 | } 58 | $NewVersionList = $NewVersionArray -join "," 59 | if($NewVersionList.Length -gt 248) { 60 | $ErrorMessage = 'The versionlist "{0}" is too long!' -f $NewVersionList 61 | Write-Warning $ErrorMessage 62 | return $VersionList 63 | } 64 | $NewVersionList 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vscode-dynamics-nav README 2 | 3 | ## Links 4 | * Checkout the [wiki](https://github.com/CloudReadySoftware/DynamicsNAVSCM/wiki) to see how to make it work currently. 5 | * Install directly in vscode: `ext install dynamics-nav-scm` 6 | * [VSCode Marketplace](https://marketplace.visualstudio.com/items?itemName=cloudreadysoftware.dynamics-nav-scm) 7 | 8 | ## Features 9 | 10 | Easy create a local Microsoft Dynamics NAV environment. 11 | Optimised for usage with git. 12 | 13 | ## Requirements 14 | 15 | * [Visual Studio Code](https://code.visualstudio.com/docs/?dv=win) 16 | * [Git](https://git-scm.com/download/win) 17 | * Windows 18 | * PowerShell 5.0 19 | * [SQL Server](https://my.visualstudio.com/Downloads?q=sql%20server%202016%20developer) named `MSSQLSERVER` 20 | * [Visual Studio Code]() running as [administrator](#run-as-admin) 21 | 22 | ## Folder Structure 23 | ``` 24 | root/ 25 | ├── modified/ --modified Object files, splitted. 26 | │ └── *.txt 27 | ├── delta/ --delta Object files, splitted. 28 | │ └── *.txt 29 | ├── addin/ --Add-ins for your solution. DLL files. 30 | │ └── *.dll 31 | ├── reportlayouts/ --RDLC and Word layouts for your solution 32 | │ └── *.rdlc 33 | ├── addinresources/ --Add-in resources, zip-files for your add-in. 34 | │ └── *.zip 35 | └── original/ --Original objects files, splitted. Should be your base version. Modified files only. 36 | └── *.txt 37 | ``` 38 | 39 | ## Run as admin 40 | ![Run as admin setting](images/runasadmin.png) 41 | 42 | ## Build from source 43 | 44 | ### Requirements 45 | * [Node.js](https://nodejs.org) 46 | * [gulp](gulpjs.com) 47 | ### Optional 48 | * Publishing/signing: `npm install -g vsce` 49 | 50 | 51 | ## Authors 52 | 53 | * Jens Rasmus Rasmussen - [Elbek & Vejrup](https://elbek-vejrup.dk/) 54 | * [Jonas B. Andersen](https://github.com/joandrsn) - [Elbek & Vejrup](https://elbek-vejrup.dk/) 55 | * Morten Braemer Jensen - [Elbek & Vejrup](https://elbek-vejrup.dk/) 56 | * [Soren Klemmensen](https://github.com/MicrosoftDynamicsNAV) - [Cloud Ready Software](http://cloud-ready-software.com/) -------------------------------------------------------------------------------- /src/logging.ts: -------------------------------------------------------------------------------- 1 | import { OutputChannel, window } from 'vscode'; 2 | import * as formatduration from 'format-duration'; 3 | import * as timestamp from 'time-stamp'; 4 | 5 | export interface ILogger { 6 | LogOutput(data: string); 7 | LogError(data: string); 8 | LogStart(data: string); 9 | LogEnd(exitcode: number, duration: number); 10 | } 11 | 12 | export class OutputLogger implements ILogger { 13 | private static instance: OutputLogger; 14 | private channel: OutputChannel = null; 15 | private static readonly channelName: string = 'NAV developer extension'; 16 | 17 | static getInstance() { 18 | if(!this.instance) { 19 | this.instance = new OutputLogger(); 20 | } 21 | return this.instance; 22 | } 23 | 24 | private constructor() { 25 | this.channel = window.createOutputChannel(OutputLogger.channelName); 26 | } 27 | LogOutput(data: string) { 28 | this.channel.appendLine(data); 29 | } 30 | LogError(data: string) { 31 | this.channel.appendLine(data); 32 | } 33 | LogStart(data: string) { 34 | this.channel.appendLine('Started function.'); 35 | } 36 | LogEnd(exitcode: number, duration: number) { 37 | let text = logDataEnd(exitcode) + "Duration: " + formatduration(duration); 38 | this.channel.appendLine(text); 39 | } 40 | } 41 | 42 | export class ConsoleLogger implements ILogger { 43 | private static instance: ConsoleLogger; 44 | 45 | static getInstance() { 46 | if(!this.instance) { 47 | this.instance = new ConsoleLogger(); 48 | } 49 | return this.instance; 50 | } 51 | 52 | LogOutput(data: string) { 53 | console.log(appendTimestamp(data)); 54 | } 55 | LogError(data: string) { 56 | console.error(appendTimestamp(data)); 57 | } 58 | LogStart(command: string) { 59 | console.log(appendTimestamp('Started function.\n\n' + command + '\n')); 60 | } 61 | LogEnd(exitcode: number, duration: number) { 62 | let text = logDataEnd(exitcode) + "Duration: " + formatduration(duration) + '\n' + '-'.repeat(30); 63 | console.log(appendTimestamp(text)); 64 | } 65 | } 66 | 67 | function logDataEnd(exitcode: number) { 68 | if(exitcode === 0) 69 | return ""; 70 | return "Something went wrong\n"; 71 | } 72 | 73 | function appendTimestamp(line: string) { 74 | return '[' + timestamp('HH:mm:ss') + '] ' + line; 75 | } -------------------------------------------------------------------------------- /src/powershell/modules/compile_objects.psm1: -------------------------------------------------------------------------------- 1 | function Start-IDECompileNAVObject 2 | { 3 | [CmdletBinding()] 4 | Param( 5 | [Parameter(Mandatory)] 6 | [String[]]$Modules, 7 | [Parameter(Mandatory)] 8 | [String]$NAVIDE, 9 | [Parameter(Mandatory)] 10 | [String]$DatabaseName, 11 | [Parameter(Mandatory)] 12 | [Switch]$Background 13 | ) 14 | Process 15 | { 16 | if($Background) 17 | { 18 | Start-BackgroundCompile -Modules $Modules -NAVIDE $NAVIDE -DatabaseName $DatabaseName 19 | } 20 | else 21 | { 22 | Start-CompileNAVObjects -DatabaseName $DatabaseName 23 | } 24 | } 25 | } 26 | 27 | function Start-BackgroundCompile 28 | { 29 | [CmdletBinding()] 30 | Param( 31 | [Parameter(Mandatory)] 32 | [String[]]$Modules, 33 | [Parameter(Mandatory)] 34 | [String]$NAVIDE, 35 | [Parameter(Mandatory)] 36 | [String]$DatabaseName 37 | ) 38 | Process 39 | { 40 | $scriptfolder = Split-Path $PSScriptRoot 41 | $ScriptFile = Join-Path $scriptfolder "compile_obj.ps1" 42 | 43 | $ModulesParameter = '-Modules "{0}"' -f ($Modules -join '","') 44 | $NAVIDEParameter = '-NAVIDE "{0}"' -f $NAVIDE 45 | $DatabaseNameParameter = '-DatabaseName "{0}"' -f $DatabaseName 46 | $ForegroundParameter = '-Foreground' 47 | $File = '-File {0}' -f $ScriptFile 48 | Start-Process "powershell.exe" -ArgumentList "-NoProfile",$File,$ModulesParameter,$NAVIDEParameter,$DatabaseNameParameter,$ForegroundParameter -WindowStyle Hidden #Normal/Hidden 49 | } 50 | } 51 | 52 | function Start-CompileNAVObjects 53 | { 54 | [CmdletBinding()] 55 | Param( 56 | [Parameter(Mandatory)] 57 | [String]$DatabaseName 58 | ) 59 | Process 60 | { 61 | $objectTypes = 'Table','Page','Report','Codeunit','Query','XMLport','MenuSuite' 62 | $jobs = @() 63 | foreach($objectType in $objectTypes) 64 | { 65 | $filter = "Type=$objectType" 66 | $jobs += Compile-NAVApplicationObject $DatabaseName -Filter $filter -Recompile -SynchronizeSchemaChanges "No" -AsJob 67 | } 68 | Receive-Job -Job $jobs -Wait 69 | Compile-NAVApplicationObject $DatabaseName -Filter "Modified=1" -Recompile -SynchronizeSchemaChanges "No" 70 | Compile-NAVApplicationObject $DatabaseName -SynchronizeSchemaChanges "No" 71 | 72 | Remove-Item (Join-Path $env:temp "NAVIDE") -Recurse -Force 73 | } 74 | } 75 | 76 | Export-ModuleMember -Function "Start-IDECompileNAVObject","Start-CompileNAVObjets" 77 | -------------------------------------------------------------------------------- /src/newversion.ts: -------------------------------------------------------------------------------- 1 | import { InputBoxOptions, window } from 'vscode'; 2 | import { Settings } from './settings'; 3 | import { ConsoleLogger, OutputLogger } from './logging'; 4 | 5 | let consoleLogger = ConsoleLogger.getInstance(); 6 | let outputLogger = OutputLogger.getInstance(); 7 | 8 | function incrementVersionNumber(solutionname: string, currentversionNumber: string) { 9 | if(currentversionNumber === ""){ 10 | currentversionNumber = "1"; 11 | } 12 | solutionname = solutionname.toUpperCase(); 13 | let result = currentversionNumber.match(/(\d+)\.?(\d*)\.?(\d*)\.?(\d*)/); 14 | let versionArray = [ 15 | result[1], 16 | result[2], 17 | result[3], 18 | result[4] 19 | ]; 20 | let versionNumberArray: number[] = []; 21 | for(let i = 0;i < 4;i++) { 22 | let integervalue = parseInt(versionArray[i]); 23 | if(isNaN(integervalue)){ 24 | integervalue = 0; 25 | } 26 | versionNumberArray[i] = integervalue; 27 | } 28 | versionNumberArray[3] += 1; 29 | let versionnumber = versionNumberArray.join('.'); 30 | return `${solutionname}${versionnumber}`; 31 | } 32 | 33 | function validateVersionNumber(versiontext: string, solutionname: string) { 34 | solutionname = solutionname.toUpperCase(); 35 | let success = true; 36 | let errormsg = ''; 37 | let match = versiontext.match(/^([a-zA-Z]+)\d+\.?\d*\.?\d*\.?\d*/); 38 | if(!match){ 39 | errormsg = "Version not matching normal versioning."; 40 | success = false; 41 | } 42 | if(match[0] !== versiontext) { 43 | errormsg = "Contains illegal characters."; 44 | success = false; 45 | } 46 | if(match[1] !== solutionname) { 47 | errormsg = `Solutionname ${solutionname}`; 48 | success = false; 49 | } 50 | outputLogger.LogError(errormsg); 51 | return success; 52 | } 53 | 54 | export function inputNewVersionNumber(settings: Object, cb?: Function) { 55 | let solutionName = settings[Settings.DATABASENAME]; 56 | let lastversionNumber = settings[Settings.SOLUTIONVERSION]; 57 | let versionNumber = incrementVersionNumber(solutionName, lastversionNumber); 58 | let options: InputBoxOptions = { 59 | password: false, 60 | placeHolder: 'Format: XXX11(.11.2.1), Fx: EVFOR11.2.3.4', 61 | prompt: 'The next versionnumber', 62 | value: versionNumber 63 | } 64 | consoleLogger.LogOutput(`Suggested versionnumber: '${versionNumber}'`); 65 | let inputBox = window.showInputBox(options); 66 | inputBox.then((newversionNumber: string) => { 67 | if(newversionNumber === "") { 68 | newversionNumber = incrementVersionNumber(solutionName, lastversionNumber); 69 | } 70 | if(!validateVersionNumber(newversionNumber, solutionName)) { 71 | consoleLogger.LogError(`'${newversionNumber}' didn't validate.`); 72 | return; 73 | } 74 | let newVersionOutput = `'${newversionNumber}' is the new version.`; 75 | consoleLogger.LogOutput(newVersionOutput); 76 | outputLogger.LogOutput(newVersionOutput); 77 | if(cb) { 78 | cb(settings, newversionNumber); 79 | } 80 | }); 81 | } -------------------------------------------------------------------------------- /src/powershell/new_env.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | 3 | Param( 4 | [Parameter(Mandatory)] 5 | [String[]]$Modules, 6 | [Parameter(Mandatory)] 7 | [String]$Zip, 8 | [Parameter(Mandatory)] 9 | [String]$ServiceInstanceName, 10 | [Parameter(Mandatory)] 11 | [String]$NSTFolder, 12 | [Parameter(Mandatory)] 13 | [String]$RTCFolder, 14 | [Parameter(Mandatory)] 15 | [String]$Addinsfolder, 16 | [Parameter(Mandatory)] 17 | [String]$RTCAddinsFolder, 18 | [Parameter(Mandatory)] 19 | [String]$NSTAddinsFolder, 20 | [Parameter(Mandatory)] 21 | [String]$LicenseFile, 22 | [Parameter(Mandatory)] 23 | [Int]$UIDOffset, 24 | [Parameter(Mandatory)] 25 | [String]$DatabaseName, 26 | [Parameter(Mandatory)] 27 | [String]$NSTEXE 28 | ) 29 | 30 | $UnloadedModules = @() 31 | foreach($Module in $Modules) 32 | { 33 | if(($Module -match "[/\\]") -and (-not (Test-Path $Module))){ 34 | $UnloadedModules += $Module 35 | } else { 36 | Import-Module $Module 37 | Set-Location $env:SystemDrive 38 | } 39 | } 40 | 41 | Write-Host "TODO: Check if db server and db name exists..." 42 | 43 | Assert-AdminProcess 44 | 45 | $NSTFolderMissing = -not (Test-Path -Path $NSTFolder -PathType Container) 46 | $RTCFolderMissing = -not (Test-Path -Path $RTCFolder -PathType Container) 47 | if($NSTFolderMissing -or $RTCFolderMissing) 48 | { 49 | Expand-NAVArchive -ZipFile $Zip -NSTFolder $NSTFolder -RTCFolder $RTCFolder -ServiceTier $NSTFolderMissing -RoleTailoredClient $RTCFolderMissing 50 | } 51 | 52 | foreach($UnloadedModule in $UnloadedModules) 53 | { 54 | Import-Module $UnloadedModule 55 | } 56 | 57 | #TODO: Remove generation of $FullServiceName 58 | $FullServiceName = 'MicrosoftDynamicsNavServer${0}' -f $ServiceInstanceName 59 | 60 | $Service = Get-Service -Name $FullServiceName -ErrorAction "SilentlyContinue" 61 | 62 | if(-not $Service) 63 | { 64 | New-NAVServiceTier -NSTFolder $NSTFolder -DatabaseName $DatabaseName -ServiceInstanceName $ServiceInstanceName -NSTEXE $NSTEXE 65 | $Service = Get-Service -Name $FullServiceName 66 | } 67 | 68 | if($Service.Status -ne "Running") 69 | { 70 | Start-Service $FullServiceName 71 | } 72 | 73 | Test-NAVInstanceWindows10 -NAVServiceInstance $ServiceInstanceName 74 | 75 | Import-NAVLicense -NAVLicenseFile $LicenseFile -ServiceInstanceName $ServiceInstanceName 76 | Stop-Service $FullServiceName 77 | Install-NAVAddIns -SourceAddinsFolder $Addinsfolder -RTCAddinsFolder $RTCAddinsFolder -NSTAddinsFolder $NSTAddinsFolder 78 | 79 | Start-Service $FullServiceName 80 | Test-NAVInstanceWindows10 -NAVServiceInstance $ServiceInstanceName 81 | 82 | 83 | $username = whoami 84 | try 85 | { 86 | $isNAVUser = Get-NAVServerUser -ServerInstance $ServiceInstanceName | Where-Object {$PSItem.UserName -eq $username} 87 | if(-not $isNAVUser) 88 | { 89 | $null = New-NAVServerUser -WindowsAccount $username -ServerInstance $ServiceInstanceName 90 | } 91 | 92 | $isSuperUser = Get-NAVServerUserPermissionSet -PermissionSetId "SUPER" -WindowsAccount $username -ServerInstance $ServiceInstanceName 93 | if(-not $isSuperUser) 94 | { 95 | $null = New-NAVServerUserPermissionSet -PermissionSetId "SUPER" -WindowsAccount $username -ServerInstance $ServiceInstanceName 96 | } 97 | } 98 | catch 99 | { 100 | 101 | } 102 | 103 | Set-UIDOffset -UIDOffset $UIDOffset -DatabaseName $DatabaseName -------------------------------------------------------------------------------- /src/powershell/modules/copy_db.psm1: -------------------------------------------------------------------------------- 1 | #Requires -Modules SQLPS 2 | 3 | function Copy-NAVDatabase 4 | { 5 | [CmdletBinding()] 6 | Param( 7 | [Parameter(Mandatory)] 8 | [String]$SourceDatabaseInstance, 9 | [Parameter(Mandatory)] 10 | [String]$SourceDatabaseName, 11 | [Parameter(Mandatory)] 12 | [String]$CommonSQLLocation, 13 | [Parameter(Mandatory)] 14 | [String]$DestinationDatabaseName 15 | ) 16 | Process 17 | { 18 | $null = Test-SQLConnection -ServerInstance $SourceDatabaseInstance -DatabaseName $SourceDatabaseName 19 | $null = Test-SQLConnection -ServerInstance $env:computername 20 | Backup-SqlDatabase -BackupAction Files -CopyOnly -ServerInstance $SourceDatabaseInstance -Database $SourceDatabaseName -BackupFile $CommonSQLLocation 21 | $LogicalFileNames = Get-LogicalFilenames -SQLInstance $SourceDatabaseInstance -Database $SourceDatabaseName 22 | $NewLocations = Get-SQLFileLocation -TargetSQLInstance $env:computername -TargetDatabase $DestinationDatabaseName -LogicalDataFilename $LogicalFileNames.Data -LogicalLogFilename $LogicalFileNames.Log 23 | Restore-SqlDatabase -ServerInstance $env:computername -Database $DestinationDatabaseName -BackupFile $CommonSQLLocation -RelocateFile $NewLocations -ReplaceDatabase 24 | Remove-Item $CommonSQLLocation 25 | } 26 | } 27 | 28 | function Test-SQLConnection 29 | { 30 | [CmdletBinding()] 31 | Param( 32 | [Parameter(Mandatory)] 33 | [String]$ServerInstance, 34 | [Parameter()] 35 | [String]$DatabaseName = "master", 36 | [Parameter()] 37 | [Int]$ConnectionTimeout = 2 38 | ) 39 | Process 40 | { 41 | $dbConnection = New-Object "System.Data.SqlClient.SqlConnection" 42 | $dbConnection.ConnectionString = "Data Source=$ServerInstance; Database=$DatabaseName;` 43 | Integrated Security=True;Connection Timeout=$ConnectionTimeout" 44 | try { 45 | $null = $dbConnection.Open() 46 | $dbConnection.Close() 47 | return $true 48 | } catch { 49 | throw "Could not establish connection to server: '$ServerInstance', database: '$DatabaseName'" 50 | } 51 | } 52 | } 53 | 54 | function Get-SQLFileLocation 55 | { 56 | [CmdletBinding()] 57 | Param( 58 | [Parameter(Mandatory)] 59 | [String]$TargetSQLInstance, 60 | [Parameter(Mandatory)] 61 | [String]$TargetDatabase, 62 | [Parameter(Mandatory)] 63 | [String]$LogicalDataFilename, 64 | [Parameter(Mandatory)] 65 | [String]$LogicalLogFilename 66 | ) 67 | Process 68 | { 69 | 70 | $databaseinstance = New-Object ('Microsoft.SqlServer.Management.Smo.Server') $TargetSQLInstance 71 | $logfileName = "{0}_Log.ldf" -f $TargetDatabase 72 | $dataFilename = "{0}.mdf" -f $TargetDatabase 73 | $logpath = Join-Path $databaseinstance.Settings.DefaultLog $logfileName 74 | $datapath = Join-Path $databaseinstance.Settings.DefaultFile $dataFilename 75 | 76 | $DataSQLFileLocation = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile($LogicalDataFilename, $datapath) 77 | $LogSQLFileLocation = New-Object Microsoft.SqlServer.Management.Smo.RelocateFile($LogicalLogFilename, $logpath) 78 | @($DataSQLFileLocation, $LogSQLFileLocation) 79 | } 80 | } 81 | 82 | function Get-LogicalFilenames 83 | { 84 | [CmdletBinding()] 85 | Param( 86 | [Parameter(Mandatory)] 87 | [String]$SQLInstance, 88 | [Parameter(Mandatory)] 89 | [String]$Database 90 | ) 91 | Process 92 | { 93 | $databaseInfo = Get-SQLDatabase -ServerInstance $SQLInstance -Name $Database 94 | [PSCustomObject]@{ 95 | "Data" = $databaseInfo.FileGroups.Files.Name 96 | "Log" = $databaseInfo.LogFiles.Name 97 | } 98 | Write-Output $object 99 | } 100 | } 101 | 102 | Export-ModuleMember -Function "Copy-NAVDatabase" -------------------------------------------------------------------------------- /src/powershell/modules/service_administration.psm1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | 3 | function New-NAVServiceTier 4 | { 5 | [CmdletBinding()] 6 | Param( 7 | ) 8 | Process 9 | { 10 | $ConfigLocation = Join-Path $NSTFolder "Instances" 11 | $ConfigFile = Copy-NAVConfig -ServiceFolder $NSTFolder -DatabaseName $NAVDatabaseName -ServiceInstanceName $NAVServiceInstance -ConfigLocation $ConfigLocation 12 | $NAVServer = Join-Path $NSTFolder "Microsoft.Dynamics.Nav.Server.exe" 13 | $binPath = "`"{1}`" `${0} config `"{2}`"" -f $NAVServiceInstance, $NAVServer, $ConfigFile 14 | $Dependencies = "HTTP","NetTcpPortSharing" 15 | $DisplayName = "Microsoft Dynamics NAV Server [{0}]" -f $NAVServiceInstance 16 | $Description = "Service handling requests to Microsoft Dynamics NAV application." 17 | $null = New-Service -Name $NAVServiceInstanceName -BinaryPathName $binPath -DisplayName $DisplayName -Description $Description -StartupType Automatic -DependsOn $Dependencies 18 | } 19 | } 20 | 21 | function Set-ServiceCredential 22 | { 23 | [CmdletBinding()] 24 | param( 25 | [Parameter(Mandatory)] 26 | [String]$ServiceInstanceName 27 | ) 28 | $ServiceCredential = Get-Credential -Message "Credentials for ''$ServiceInstanceName'" -UserName $(whoami) 29 | $filter = 'Name = "MicrosoftDynamicsNavServer${0}"' -f $ServiceInstanceName 30 | $services = Get-WmiObject -Class "Win32_Service" -Filter $filter 31 | if ((-not $services) -or ($services.Count -ne 1)) { 32 | throw "Unable to find services named '$ServiceInstanceName'." 33 | } 34 | Set-ServiceCredentials -Service $services -ServiceCredential $ServiceCredential 35 | } 36 | 37 | function Set-ServiceCredentials 38 | { 39 | [CmdletBinding()] 40 | param( 41 | [Parameter(Mandatory,ValueFromPipeline)] 42 | [System.Management.ManagementObject[]] $Service, 43 | [Parameter(Mandatory)] 44 | [Management.Automation.PSCredential] $ServiceCredential 45 | ) 46 | process { 47 | $changeService = $Service.Change($null, # DisplayName 48 | $null, # PathName 49 | $null, # ServiceType 50 | $null, # ErrorControl 51 | $null, # StartMode 52 | $null, # DesktopInteract 53 | $serviceCredential.UserName, # StartName 54 | $serviceCredential.GetNetworkCredential().Password, # StartPassword 55 | $null, # LoadOrderGroup 56 | $null, # LoadOrderGroupDependencies 57 | $null) # ServiceDependencies 58 | $returnValue = $changeService.ReturnValue 59 | $errorMessage = "Error setting credentials for service '$serviceName'" 60 | switch ( $returnValue ) { 61 | 0 { Write-Verbose "Set credentials for service '$serviceName'" } 62 | 1 { Write-Error "$errorMessage - Not Supported" } 63 | 2 { Write-Error "$errorMessage - Access Denied" } 64 | 3 { Write-Error "$errorMessage - Dependent Services Running" } 65 | 4 { Write-Error "$errorMessage - Invalid Service Control" } 66 | 5 { Write-Error "$errorMessage - Service Cannot Accept Control" } 67 | 6 { Write-Error "$errorMessage - Service Not Active" } 68 | 7 { Write-Error "$errorMessage - Service Request timeout" } 69 | 8 { Write-Error "$errorMessage - Unknown Failure" } 70 | 9 { Write-Error "$errorMessage - Path Not Found" } 71 | 10 { Write-Error "$errorMessage - Service Already Stopped" } 72 | 11 { Write-Error "$errorMessage - Service Database Locked" } 73 | 12 { Write-Error "$errorMessage - Service Dependency Deleted" } 74 | 13 { Write-Error "$errorMessage - Service Dependency Failure" } 75 | 14 { Write-Error "$errorMessage - Service Disabled" } 76 | 15 { Write-Error "$errorMessage - Service Logon Failed" } 77 | 16 { Write-Error "$errorMessage - Service Marked For Deletion" } 78 | 17 { Write-Error "$errorMessage - Service No Thread" } 79 | 18 { Write-Error "$errorMessage - Status Circular Dependency" } 80 | 19 { Write-Error "$errorMessage - Status Duplicate Name" } 81 | 20 { Write-Error "$errorMessage - Status Invalid Name" } 82 | 21 { Write-Error "$errorMessage - Status Invalid Parameter" } 83 | 22 { Write-Error "$errorMessage - Status Invalid Service Account" } 84 | 23 { Write-Error "$errorMessage - Status Service Exists" } 85 | 24 { Write-Error "$errorMessage - Service Already Paused" } 86 | } 87 | } 88 | } 89 | 90 | Export-ModuleMember -Function "New-NAVServiceTier","Set-ServiceCredential" -------------------------------------------------------------------------------- /src/powershell.ts: -------------------------------------------------------------------------------- 1 | import { ILogger } from './logging'; 2 | import * as PowerShellRunner from 'powershell'; 3 | 4 | const enum MessageLevel { 5 | Start, 6 | End, 7 | Output, 8 | Error 9 | } 10 | 11 | export class Powershell { 12 | 13 | private startTime: Date; 14 | private endTime: Date; 15 | private path: string; 16 | modules: string[]; 17 | settings: Object; 18 | observers: ILogger[]; 19 | 20 | constructor(path: string) { 21 | this.path = path; 22 | } 23 | 24 | getArrayParameter(array: string[]) { 25 | let result = null; 26 | if(array){ 27 | let parameterString = array.join("','"); 28 | result = `'${parameterString}'`; 29 | } 30 | return result; 31 | } 32 | 33 | private getModuleString() { 34 | let modulestring = this.getArrayParameter(this.modules); 35 | if(modulestring) { 36 | return `-Modules ${modulestring}` 37 | } 38 | } 39 | 40 | 41 | private addQuotes(parameter: string) { 42 | if (typeof parameter === 'number') 43 | return parameter; 44 | if (typeof parameter === 'boolean') 45 | return `$${parameter}` 46 | if (parameter.startsWith("'")) { 47 | return parameter; 48 | } 49 | return `'${parameter}'`; 50 | } 51 | 52 | private parseSetting(parameter: any) { 53 | if(Array.isArray(parameter)) 54 | return "'" + parameter.join("','") + "'"; 55 | if(typeof parameter === 'number') 56 | return parameter; 57 | if(typeof parameter === 'boolean') 58 | return `$${parameter}` 59 | if(parameter.startsWith("'")){ 60 | return parameter; 61 | } 62 | return `'${parameter}'`; 63 | } 64 | 65 | private getParameterString() { 66 | let result = ""; 67 | if(this.settings) { 68 | let settingsarray = []; 69 | let keys = Object.keys(this.settings); 70 | for(let i = 0;i < keys.length; i++) { 71 | let currentKey = keys[i]; 72 | let currentvalue = this.settings[currentKey]; 73 | let parameterString = Array.isArray(currentvalue) ? this.getArrayParameter(currentvalue) : this.addQuotes(currentvalue); 74 | settingsarray.push(`-${currentKey} ${parameterString}`); 75 | } 76 | result = settingsarray.join(' '); 77 | } 78 | return result; 79 | } 80 | 81 | private getScriptString() { 82 | let result = "$ErrorActionPreference = 'Stop'\n"; 83 | let moduleString = this.getModuleString(); 84 | let settingsString = this.getParameterString(); 85 | result += `$DebugPreference = 'Continue'\n`; 86 | result += this.path; 87 | if(moduleString) 88 | result += ` ${moduleString}` 89 | if(settingsString) 90 | result += ` ${settingsString}` 91 | return result; 92 | } 93 | 94 | public invoke() { 95 | let command = this.getScriptString(); 96 | this.runPowershell(command); 97 | } 98 | 99 | private runPowershell(command: string) { 100 | let options = { 101 | debug: false, 102 | noprofile: true, 103 | executionpolicy: "Unrestricted" 104 | }; 105 | this.startTime = new Date(); 106 | let ps = new PowerShellRunner(command, options); 107 | 108 | this.LogStart(command); 109 | 110 | ps.proc.stderr.on('data', data => { 111 | this.LogError(data); 112 | }); 113 | ps.proc.stdout.on('data', data => { 114 | this.LogOutput(data); 115 | }); 116 | ps.proc.on("close", exitcode => { 117 | this.endTime = new Date(); 118 | this.LogEnd(exitcode, this.endTime.valueOf() - this.startTime.valueOf()); 119 | }); 120 | } 121 | 122 | private FormatProcessOutput(data: string) { 123 | return data.split(/\n/); 124 | } 125 | 126 | private LogStart(command: string) { 127 | if(this.observers) { 128 | this.observers.forEach(observer => { 129 | observer.LogStart(command); 130 | }); 131 | } 132 | } 133 | private LogEnd(exitcode: number, duration: number) { 134 | if(this.observers) { 135 | this.observers.forEach(observer => { 136 | observer.LogEnd(exitcode, duration); 137 | }); 138 | } 139 | } 140 | private LogError(data: string) { 141 | if(this.observers) { 142 | let dataArray: string[] = this.FormatProcessOutput(data); 143 | this.observers.forEach(observer => { 144 | dataArray.forEach(line => { 145 | observer.LogError(line); 146 | }); 147 | }); 148 | } 149 | } 150 | private LogOutput(data: string) { 151 | if(this.observers) { 152 | let dataArray: string[] = this.FormatProcessOutput(data); 153 | this.observers.forEach(observer => { 154 | dataArray.forEach(line => { 155 | observer.LogOutput(line); 156 | }); 157 | }); 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /src/powershell/modules/import_objects.psm1: -------------------------------------------------------------------------------- 1 | #Requires -Modules Microsoft.Dynamics.Nav.Ide,Microsoft.Dynamics.Nav.Model.Tools 2 | 3 | 4 | function Import-IDENAVObject 5 | { 6 | [CmdletBinding()] 7 | Param( 8 | [Parameter(Mandatory)] 9 | [String]$ObjectsFolder, 10 | [Parameter(Mandatory)] 11 | [String]$Workspacefolder, 12 | [Parameter(Mandatory)] 13 | [String]$SolutionName, 14 | [Parameter(Mandatory)] 15 | [String]$DatabaseName, 16 | [Parameter(Mandatory)] 17 | [String]$LastImportGitHashFilepath 18 | ) 19 | Process 20 | { 21 | Push-Location $Workspacefolder 22 | if(-not (Test-GitInstalled)) { 23 | throw "Git not installed? Cannot be used." 24 | } 25 | $CurrentHash = Get-CurrentGitHash 26 | $Foldername = Split-Path $ObjectsFolder -Leaf 27 | $files = Get-GitImportObjects -LastImportGitHashFilepath $LastImportGitHashFilepath -CurrentHash $CurrentHash -Foldername $Foldername 28 | $tempfolder = Get-TempFolder 29 | $SingleFile = Join-Path $tempfolder "file.txt" 30 | $null = Join-NAVApplicationObjectFile -Source $files -Destination $SingleFile 31 | Import-NAVApplicationObject -Path $SingleFile -DatabaseName $DatabaseName -ImportAction "Overwrite" -SynchronizeSchemaChanges "No" -Confirm:$false 32 | Remove-Item $tempfolder -Recurse -Force 33 | Sync-NAVDatabaseObjects 34 | Set-LastImportedGitHash -GitHash $CurrentHash -File $LastImportGitHashFilepath 35 | } 36 | } 37 | 38 | 39 | function Sync-NAVDatabaseObjects 40 | { 41 | [CmdletBinding()] 42 | Param( 43 | ) 44 | Process 45 | { 46 | $objectFiles = Join-Path $ObjectsFolder "*.txt" 47 | $currentFiles = Get-ChildItem $objectFiles 48 | $databaseObjects = Get-NAVDatabaseObjects 49 | $basenames = $currentFiles.BaseName 50 | foreach($databaseObject in $databaseObjects) 51 | { 52 | if(-not ($basenames -contains $databaseObject)) 53 | { 54 | $Object = Convert-NAVObjectFileName -FileName $databaseObject 55 | $Filter = "Type={0};Id={1}" -f $Object.Type,$Object.ID 56 | Delete-NAVApplicationObject -DatabaseName $NAVDatabaseName -Filter $Filter -SynchronizeSchemaChanges "No" -Confirm:$false 57 | } 58 | } 59 | } 60 | } 61 | 62 | function Convert-NAVObjectFileName 63 | { 64 | [CmdletBinding()] 65 | Param( 66 | [Parameter()] 67 | [String]$FileName 68 | ) 69 | Process 70 | { 71 | switch ($FileName.SubString(0, 3)) 72 | { 73 | "COD" {$Type = "Codeunit"} 74 | "MEN" {$Type = "Menusuite"} 75 | "PAG" {$Type = "Page"} 76 | "QUE" {$Type = "Query"} 77 | "REP" {$Type = "Report"} 78 | "TAB" {$Type = "Table"} 79 | "XML" {$Type = "XMLport"} 80 | default { throw "Unknown type!"} 81 | } 82 | [PSCustomObject]@{ 83 | "Type" = $Type 84 | "ID" = $FileName.SubString(3) 85 | } 86 | } 87 | } 88 | 89 | function Get-GitImportObjects 90 | { 91 | [CmdletBinding()] 92 | Param( 93 | [Parameter(Mandatory)] 94 | [String]$LastImportGitHashFilepath, 95 | [Parameter(Mandatory)] 96 | [String]$CurrentHash, 97 | [Parameter(Mandatory)] 98 | [String]$Foldername 99 | ) 100 | Process 101 | { 102 | $hash = Get-LastImportedHash -File $LastImportGitHashFilepath 103 | Get-DiffInObjects -LastImportedHash $hash -CurrentHash $CurrentHash -Foldername $Foldername 104 | } 105 | } 106 | 107 | function Get-CurrentGitHash 108 | { 109 | [CmdletBinding()] 110 | Param( 111 | ) 112 | Process 113 | { 114 | Invoke-Expression -Command "git rev-parse HEAD" 115 | } 116 | } 117 | 118 | function Get-LastImportedHash 119 | { 120 | [CmdletBinding()] 121 | Param( 122 | [Parameter(Mandatory)] 123 | [String]$File 124 | ) 125 | Process 126 | { 127 | $result = "" 128 | if(Test-Path -PathType Leaf $File) { 129 | $result = Get-Content $file 130 | } else { 131 | $result = Invoke-Expression "git rev-list --max-parents=0 HEAD" 132 | } 133 | $result 134 | } 135 | } 136 | 137 | function Test-GitInstalled 138 | { 139 | [CmdletBinding()] 140 | Param( 141 | ) 142 | Process 143 | { 144 | $command = "git status" 145 | $result = $true 146 | try { 147 | $response = Invoke-Expression -Command $command 148 | 149 | } catch { 150 | $result = $false 151 | } 152 | $result 153 | } 154 | } 155 | 156 | function Get-DiffInObjects 157 | { 158 | [CmdletBinding()] 159 | Param( 160 | [Parameter()] 161 | [String]$LastImportedHash, 162 | [Parameter(Mandatory)] 163 | [String]$CurrentHash, 164 | [Parameter(Mandatory)] 165 | [String]$Foldername 166 | ) 167 | Process 168 | { 169 | $command = 'git diff --name-only "{0}" "{1}"' -f $LastImportedHash, $CurrentHash 170 | $result = Invoke-Expression -Command $command 171 | $regex = '^{0}/.*\.txt$' -f $Foldername 172 | $resultObjects = $result | Where-Object {$PSItem -imatch $regex} 173 | $currentpath = Get-Location 174 | $currentPath = $currentpath.Path 175 | foreach($object in $resultObjects) { 176 | Join-Path $currentpath $object 177 | } 178 | } 179 | } 180 | 181 | function Set-LastImportedGitHash 182 | { 183 | [CmdletBinding()] 184 | Param( 185 | [Parameter(Mandatory)] 186 | [String]$GitHash, 187 | [Parameter(Mandatory)] 188 | [String]$File 189 | ) 190 | Process 191 | { 192 | Out-File -FilePath $File -Encoding utf8 -NoNewline -InputObject $GitHash 193 | } 194 | } 195 | 196 | Export-ModuleMember -Function "Import-IDENAVObject" -------------------------------------------------------------------------------- /src/actions.ts: -------------------------------------------------------------------------------- 1 | import { Powershell } from './powershell'; 2 | import { Settings } from './settings'; 3 | import * as scripts from './scripts'; 4 | import * as modules from './modules'; 5 | import * as workspacefolder from './folders'; 6 | import { ConsoleLogger, OutputLogger } from './logging'; 7 | import { selectItem, ExportOption } from './exportselection' 8 | import { inputNewVersionNumber } from './newversion'; 9 | import { createFolderIfNotExist, createGitIgnorefile } from './newenv'; 10 | 11 | let observers = [ 12 | ConsoleLogger.getInstance(), 13 | OutputLogger.getInstance() 14 | ]; 15 | 16 | export function copyDB() { 17 | let settings = Settings.GetAllSettings(); 18 | let ps = new Powershell(scripts.COPY_DB); 19 | ps.observers = observers; 20 | ps.modules = [ 21 | 'SQLPS', 22 | modules.COPY_DB 23 | ]; 24 | ps.settings = { 25 | SourceDatabaseInstance: settings[Settings.REMOTEDBINSTANCE], 26 | SourceDatabaseName: settings[Settings.REMOTEDBNAME], 27 | CommonSQLLocation: settings[Settings.COMMONSQLLOCATION], 28 | DestinationDatabaseName: settings[Settings.DATABASENAME] 29 | }; 30 | ps.invoke(); 31 | } 32 | 33 | export function pickFilesExport() { 34 | selectItem(exportNAV2Git); 35 | } 36 | 37 | function exportNAV2Git(filters: boolean) { 38 | let settings = Settings.GetAllSettings(); 39 | let ps = new Powershell(scripts.NAV2GIT); 40 | ps.observers = observers; 41 | ps.modules = [ 42 | modules.COMMONFUNCTIONS, 43 | modules.EXPORT_OBJECTS, 44 | 'SQLPS', 45 | settings[Settings.IDEMODULE], 46 | settings[Settings.MODELTOOLS] 47 | ]; 48 | ps.settings = { 49 | DatabaseName: settings[Settings.DATABASENAME], 50 | NAVIDE: settings[Settings.FINSQL], 51 | DestinationFolder: workspacefolder.SOURCE, 52 | TempFolder: workspacefolder.TEMP, 53 | Filters: settings[Settings.FILTERS], 54 | DateModification: settings[Settings.DATEMODIFICATION] 55 | }; 56 | ps.invoke(); 57 | } 58 | 59 | export function compileNAVObjects() { 60 | let settings = Settings.GetAllSettings(); 61 | let ps = new Powershell(scripts.COMPILE_OBJ); 62 | ps.observers = observers; 63 | ps.modules = [ 64 | modules.COMPILE_OBJECTS, 65 | settings[Settings.IDEMODULE] 66 | ]; 67 | ps.settings = { 68 | DatabaseName: settings[Settings.DATABASENAME], 69 | NAVIDE: settings[Settings.FINSQL] 70 | } 71 | ps.invoke(); 72 | } 73 | 74 | export function removeEnvironment() { 75 | let settings = Settings.GetAllSettings(); 76 | let ps = new Powershell(scripts.REMOVE_ENV); 77 | ps.observers = observers; 78 | ps.settings = { 79 | ServiceInstanceName: settings[Settings.SERVICENAME], 80 | BaseFolder: settings[Settings.BASEFOLDER] 81 | } 82 | ps.invoke(); 83 | } 84 | 85 | export function newEnvironment() { 86 | let settings = Settings.GetAllSettings(); 87 | let ps = new Powershell(scripts.NEW_ENV); 88 | ps.observers = observers; 89 | ps.modules = [ 90 | modules.COMMONFUNCTIONS, 91 | 'SQLPS', 92 | settings[Settings.MANAGEMENTMODULE], 93 | modules.NEW_NAV 94 | ]; 95 | ps.settings = { 96 | Zip: settings[Settings.ZIP], 97 | ServiceInstanceName: settings[Settings.DATABASENAME], 98 | NSTFolder: settings[Settings.NSTFOLDER], 99 | RTCFolder: settings[Settings.RTCFOLDER], 100 | Addinsfolder: workspacefolder.ADDIN, 101 | RTCAddinsFolder: settings[Settings.RTCADDINS], 102 | NSTAddinsFolder: settings[Settings.NSTADDINS], 103 | LicenseFile: settings[Settings.LICENSEFILE], 104 | DatabaseName: settings[Settings.DATABASENAME], 105 | UIDOffset: settings[Settings.UIDOFFSET], 106 | NSTEXE: settings[Settings.NST] 107 | }; 108 | ps.invoke(); 109 | 110 | } 111 | 112 | export function startIDE() { 113 | let settings = Settings.GetAllSettings(); 114 | let ps = new Powershell(scripts.START_IDE); 115 | ps.observers = observers; 116 | ps.settings = { 117 | databasename: settings[Settings.DATABASENAME], 118 | navide: settings[Settings.FINSQL] 119 | }; 120 | ps.invoke(); 121 | } 122 | 123 | export function startShell() { 124 | let settings = Settings.GetAllSettings(); 125 | let ps = new Powershell(scripts.START_SHELL); 126 | ps.observers = observers; 127 | let modulesToImport = [ 128 | 'SQLPS', 129 | settings[Settings.MANAGEMENTMODULE], 130 | settings[Settings.IDEMODULE], 131 | settings[Settings.MODELTOOLS] 132 | ] 133 | let variablesToDeclared = [ 134 | Settings.FINSQL, settings[Settings.FINSQL], 135 | Settings.SERVICENAME, settings[Settings.SERVICENAME], 136 | Settings.RTC, settings[Settings.RTC], 137 | Settings.DATABASENAME, settings[Settings.DATABASENAME], 138 | Settings.BASEFOLDER, settings[Settings.BASEFOLDER], 139 | 'repository', workspacefolder.WORKSPACE 140 | ]; 141 | ps.settings = { 142 | Modules: ps.getArrayParameter(modulesToImport), 143 | Variables: ps.getArrayParameter(variablesToDeclared) 144 | }; 145 | ps.invoke(); 146 | } 147 | 148 | export function init_workspace() { 149 | let settings = Settings.GetAllSettings(); 150 | createFolderIfNotExist(workspacefolder.ADDIN); 151 | createFolderIfNotExist(workspacefolder.ADDINRESOURCES); 152 | createFolderIfNotExist(workspacefolder.SOURCE); 153 | createFolderIfNotExist(workspacefolder.REPORTLAYOUTS); 154 | createFolderIfNotExist(workspacefolder.TEMP); 155 | createGitIgnorefile(workspacefolder.WORKSPACE); 156 | //CreateVSCodeSettingsFile(); 157 | } 158 | 159 | export function importObjects() { 160 | let settings = Settings.GetAllSettings(); 161 | let ps = new Powershell(scripts.GIT2NAV); 162 | ps.observers = observers; 163 | ps.modules = [ 164 | modules.COMMONFUNCTIONS, 165 | settings[Settings.MODELTOOLS], 166 | settings[Settings.IDEMODULE], 167 | modules.IMPORT_OBJECTS 168 | ]; 169 | ps.settings = { 170 | ObjectsFolder: workspacefolder.SOURCE, 171 | workspacefolder: workspacefolder.WORKSPACE, 172 | SolutionName: settings[Settings.DATABASENAME], 173 | DatabaseName: settings[Settings.DATABASENAME], 174 | LastImportGitHashFilepath: workspacefolder.LASTIMPORTEDGITHASH 175 | }; 176 | ps.invoke(); 177 | } -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { WorkspaceConfiguration, workspace } from 'vscode'; 2 | import { join } from 'path'; 3 | import Helpers from './shared/helpers' 4 | 5 | export class Settings { 6 | static readonly ZIP = 'zip'; 7 | static readonly ORIGINALOBJECTS = 'originalobjects'; 8 | static readonly DATABASENAME = 'databasename'; 9 | static readonly REMOTEDBNAME = 'remotedbname'; 10 | static readonly SOLUTIONVERSION = 'solutionVersion'; 11 | static readonly ORIGNALPATH = 'originalpath'; 12 | static readonly MODIFIEDPATH = 'modifiedpath'; 13 | static readonly TARGETPATH = 'targetpath'; 14 | static readonly RESULTPATH = 'resultpath'; 15 | static readonly VERSIONS = 'versions'; 16 | static readonly INSTALLPATH = 'installpath'; 17 | static readonly REMOTEDBINSTANCE = 'remotedbinstance'; 18 | static readonly LICENSEFILE = 'licensefile'; 19 | static readonly COMMONSQLLOCATION = 'commonsqllocation'; 20 | static readonly UIDOFFSET = 'uidoffset'; 21 | static readonly BASEFOLDER = 'basefolder'; 22 | static readonly RTCFOLDER = 'rtcfolder'; 23 | static readonly NSTFOLDER = 'nstfolder'; 24 | static readonly FINSQL = 'finsql'; 25 | static readonly RTC = 'rtc'; 26 | static readonly NST = 'nst'; 27 | static readonly RTCADDINS = 'rtcaddins'; 28 | static readonly NSTADDINS = 'nstaddins'; 29 | static readonly MODELTOOLS = 'modeltools'; 30 | static readonly IDEMODULE = 'idemodule'; 31 | static readonly MANAGEMENTMODULE = 'managementmodule'; 32 | static readonly NEXTVERSIONNO = 'nextversionno'; 33 | static readonly SERVICENAME = 'servicename'; 34 | static readonly DEFAULTNAVBUILDFOLDER = 'defaultNAVBuildFolder'; 35 | static readonly NSTPATH = 'nstpath'; 36 | static readonly RTCPATH = 'rtcpath'; 37 | static readonly FILTERS = 'filters' 38 | static readonly DATEMODIFICATION = 'datemodification' 39 | private static WORKSPACEKEY: string = 'dynamicsnav'; 40 | 41 | private static config: WorkspaceConfiguration; 42 | 43 | private static readonly FINSQLEXE = 'finsql.exe'; 44 | private static readonly RTCEXE = 'Microsoft.Dynamics.Nav.Client.exe'; 45 | private static readonly NSTEXE = 'Microsoft.Dynamics.Nav.Server.exe'; 46 | private static readonly RTCFOLDERNAME = 'RTC'; 47 | private static readonly NSTFOLDERNAME = 'NST'; 48 | private static readonly MODELTOOLSDLL = 'Microsoft.Dynamics.Nav.Model.Tools.dll'; 49 | private static readonly IDEPSM = 'Microsoft.Dynamics.Nav.Ide.psm1'; 50 | private static readonly MANAGEMENTDLL = 'Microsoft.Dynamics.Nav.Management.dll'; 51 | private static readonly ADDIN = 'Add-ins'; 52 | private static readonly NSTSERVICENAME = 'MicrosoftDynamicsNavServer$' 53 | private static readonly WORKSPACESETTINGS = [Settings.DATABASENAME]; 54 | 55 | private static getSetting(key: string) 56 | { 57 | if(!this.config.has(key)) 58 | { 59 | return null; 60 | } 61 | if(!(this.WORKSPACESETTINGS.includes(key))) 62 | { 63 | return this.config.get(key); 64 | }; 65 | let inspection = this.config.inspect(key); 66 | let workspaceValue = inspection.workspaceValue; 67 | if(!workspaceValue) 68 | { 69 | Helpers.throwError(`Workspace key '${key}' needed in your setup.`); 70 | } 71 | return workspaceValue; 72 | } 73 | private static getSettings(){ 74 | let result = {}; 75 | let nstpath = this.getSetting(this.NSTPATH); 76 | let rtcpath = this.getSetting(this.RTCPATH); 77 | 78 | result[this.ZIP] = this.getSetting(this.ZIP); 79 | result[this.ORIGINALOBJECTS] = this.getSetting(this.ORIGINALOBJECTS); 80 | result[this.DATABASENAME] = this.getSetting(this.DATABASENAME); 81 | result[this.REMOTEDBNAME] = this.getSetting(this.REMOTEDBNAME); 82 | result[this.SOLUTIONVERSION] = this.getSetting(this.SOLUTIONVERSION); 83 | result[this.ORIGNALPATH] = this.getSetting(this.ORIGNALPATH); 84 | result[this.MODIFIEDPATH] = this.getSetting(this.MODIFIEDPATH); 85 | result[this.TARGETPATH] = this.getSetting(this.TARGETPATH); 86 | result[this.RESULTPATH] = this.getSetting(this.RESULTPATH); 87 | result[this.VERSIONS] = this.getSetting(this.VERSIONS); 88 | result[this.INSTALLPATH] = this.getSetting(this.INSTALLPATH); 89 | result[this.REMOTEDBINSTANCE] = this.getSetting(this.REMOTEDBINSTANCE); 90 | result[this.LICENSEFILE] = this.getSetting(this.LICENSEFILE); 91 | result[this.COMMONSQLLOCATION] = this.getSetting(this.COMMONSQLLOCATION); 92 | result[this.UIDOFFSET] = this.getSetting(this.UIDOFFSET); 93 | result[this.BASEFOLDER] = this.joinPaths([result[this.INSTALLPATH], result[this.DATABASENAME]]); 94 | result[this.RTCFOLDER] = rtcpath ? rtcpath : this.joinPaths([result[this.BASEFOLDER], this.RTCFOLDERNAME]); 95 | result[this.NSTFOLDER] = nstpath ? nstpath : this.joinPaths([result[this.BASEFOLDER], this.NSTFOLDERNAME]); 96 | result[this.FINSQL] = this.joinPaths([result[this.RTCFOLDER], this.FINSQLEXE]); 97 | result[this.RTC] = this.joinPaths([result[this.RTCFOLDER], this.RTCEXE]); 98 | result[this.NST] = this.joinPaths([result[this.NSTFOLDER], this.NSTEXE]); 99 | result[this.RTCADDINS] = this.joinPaths([result[this.RTCFOLDER], this.ADDIN, result[this.DATABASENAME]]); 100 | result[this.NSTADDINS] = this.joinPaths([result[this.NSTFOLDER], this.ADDIN, result[this.DATABASENAME]]); 101 | result[this.MODELTOOLS] = this.joinPaths([result[this.RTCFOLDER], this.MODELTOOLSDLL]); 102 | result[this.IDEMODULE] = this.joinPaths([result[this.RTCFOLDER], this.IDEPSM]); 103 | result[this.MANAGEMENTMODULE] = this.joinPaths([result[this.NSTFOLDER], this.MANAGEMENTDLL]); 104 | result[this.NEXTVERSIONNO] = `${result[this.DATABASENAME]}_DEV`; 105 | result[this.SERVICENAME] = `${this.NSTSERVICENAME}${result[this.DATABASENAME]}`; 106 | result[this.DEFAULTNAVBUILDFOLDER] = this.getSetting(this.DEFAULTNAVBUILDFOLDER); 107 | result[this.FILTERS] = this.getSetting(this.FILTERS); 108 | result[this.DATEMODIFICATION] = this.getSetting(this.DATEMODIFICATION); 109 | 110 | return result; 111 | } 112 | 113 | private static joinPaths(paths: string[]) { 114 | for(let i = 0; i < paths.length; i++) 115 | { 116 | if(!paths[i] || paths[i] === "") 117 | return null; 118 | } 119 | return join.apply(null, paths); 120 | } 121 | 122 | public static GetAllSettings() { 123 | this.config = workspace.getConfiguration(this.WORKSPACEKEY); 124 | return this.getSettings(); 125 | } 126 | } -------------------------------------------------------------------------------- /src/powershell/modules/export_objects.psm1: -------------------------------------------------------------------------------- 1 | function Export-IDENAVObject 2 | { 3 | [CmdletBinding()] 4 | Param( 5 | [Parameter(Mandatory)] 6 | [String]$DatabaseName, 7 | [Parameter(Mandatory)] 8 | [String]$DestinationFolder, 9 | [Parameter()] 10 | [String]$TempFolder, 11 | [Parameter()] 12 | [String[]]$Filters, 13 | [Parameter()] 14 | [Boolean]$DateModification 15 | ) 16 | Process 17 | { 18 | $NewTempFolder = Get-TempFolder -SourceFolder $TempFolder 19 | $SplitFolder = Join-Path $NewTempFolder "split" 20 | Export-SplitTextFile -DatabaseName $DatabaseName -DestinationFolder $SplitFolder -TempFolder $NewTempFolder -Filters $Filters 21 | Update-NAVObjectProperties -ExportedFiles $SplitFolder -OriginalFiles $DestinationFolder -DateModification $DateModification 22 | Copy-UpdatedObjects -SourceFolder $SplitFolder -DestinationFolder $DestinationFolder 23 | Reset-UnlicensedObjects -ObjectFolder $DestinationFolder 24 | Sync-NAVObjectFiles -ObjectFolder $DestinationFolder -DatabaseName $DatabaseName 25 | Remove-Item $NewTempFolder -Recurse -Force 26 | } 27 | } 28 | 29 | function Reset-UnlicensedObjects 30 | { 31 | [CmdletBinding()] 32 | Param( 33 | [Parameter(Mandatory)] 34 | [String]$ObjectFolder 35 | ) 36 | Process 37 | { 38 | $Objects = Get-ChildItem $ObjectFolder -Include "*.fob" 39 | if($Objects) { 40 | Remove-Item $Objects 41 | } 42 | } 43 | } 44 | 45 | function Copy-UpdatedObjects 46 | { 47 | [CmdletBinding()] 48 | Param( 49 | [Parameter(Mandatory)] 50 | [String]$SourceFolder, 51 | [Parameter(Mandatory)] 52 | [String]$DestinationFolder 53 | ) 54 | Process 55 | { 56 | $UpdatedObjects = Get-ChildItem $SourceFolder 57 | if(-not ($UpdatedObjects)) { 58 | return 59 | } 60 | if(-not (Test-Path $DestinationFolder)) { 61 | $null = New-Item -Path $DestinationFolder -ItemType Directory 62 | } 63 | $UpdatedFiles = $UpdatedObjects | Select-Object -ExpandProperty "FullName" 64 | Copy-Item -Path $UpdatedFiles -Destination $DestinationFolder -Force 65 | } 66 | } 67 | 68 | function Export-SplitTextFile 69 | { 70 | [CmdletBinding()] 71 | Param( 72 | [Parameter(Mandatory)] 73 | [String]$DatabaseName, 74 | [Parameter(Mandatory)] 75 | [String]$DestinationFolder, 76 | [Parameter()] 77 | [String]$TempFolder, 78 | [Parameter()] 79 | [String[]]$Filters = @('') 80 | ) 81 | [int]$count = 0 82 | foreach($Filter in $Filters) { 83 | $count += 1 84 | $foldername = Join-Path $TempFolder "export$count" 85 | $folder = New-Item -Path $Foldername -ItemType Directory 86 | $txtFile = Join-Path $folder "export.txt" 87 | $txtFile = Export-NAVApplicationObject -DatabaseName $DatabaseName -Path $txtFile -ExportTxtSkipUnlicensed -Filter $Filter 88 | if(Test-Path $txtFile) { 89 | $File = Get-Item $txtFile 90 | if($File.Length -ne 0){ 91 | Split-NAVApplicationObjectFile -Source $txtFile -Destination $DestinationFolder -PreserveFormatting -Force 92 | } 93 | } 94 | Remove-Item $folder -Recurse 95 | } 96 | } 97 | 98 | function Update-NAVObjectProperties { 99 | [CmdletBinding()] 100 | Param( 101 | [Parameter(Mandatory)] 102 | [String]$ExportedFiles, 103 | [Parameter(Mandatory)] 104 | [String]$OriginalFiles, 105 | [Parameter()] 106 | [Boolean]$DateModification = $false 107 | ) 108 | if(!$DateModification) { 109 | return 110 | } 111 | $ExportedObjects = Get-ChildItem $ExportedFiles 112 | foreach($ExportedObject in $ExportedObjects) 113 | { 114 | $OrignalObject = Join-Path $OriginalFiles $ExportedObject.Name 115 | $DateValue = Get-Date -Hour 0 -Minute 30 -Second 0 116 | if(Test-Path -Path $OrignalObject -PathType Leaf) 117 | { 118 | $OrignalInfo = Get-NAVApplicationObjectProperty -Source $OrignalObject 119 | $DateValue = "{0} {1}" -f $OrignalInfo.Date,$OrignalInfo.Time 120 | } 121 | $DateTimeProp = Get-Date -Date $DateValue -Format "G" 122 | Set-NAVApplicationObjectProperty -TargetPath $ExportedObject.FullName -DateTimeProperty $DateTimeProp 123 | } 124 | } 125 | 126 | function Sync-NAVObjectFiles 127 | { 128 | [CmdletBinding()] 129 | Param( 130 | [Parameter(Mandatory)] 131 | [String]$ObjectFolder, 132 | [Parameter(Mandatory)] 133 | [String]$DatabaseName 134 | ) 135 | Process 136 | { 137 | $objectFiles = Join-Path $ObjectFolder "*.txt" 138 | $currentFiles = Get-ChildItem $objectFiles 139 | $databaseObjects = Get-NAVDatabaseObjects -DatabaseName $DatabaseName 140 | $result = Compare-NAVExportObjects -FileList $currentFiles -DatabaseList $databaseObjects 141 | if($result.FileDeleteList -ne $null) { 142 | Remove-Item $result.FileDeleteList 143 | } 144 | if($result.DatabaseDeleteList -ne $null) { 145 | Export-FobFiles -DatabaseName $DatabaseName -DatabaseObjects $result.DatabaseDeleteList -ObjectFolder $ObjectFolder 146 | } 147 | } 148 | } 149 | 150 | function Export-FobFiles 151 | { 152 | [CmdletBinding()] 153 | Param( 154 | [Parameter(Mandatory)] 155 | [String]$Databasename, 156 | [Parameter(Mandatory)] 157 | [PSCustomObject[]]$DatabaseObjects, 158 | [Parameter(Mandatory)] 159 | [String]$ObjectFolder 160 | ) 161 | Process 162 | { 163 | foreach($DatabaseObject in $DatabaseObjects) 164 | { 165 | $Filename = "{0}.fob" -f $DatabaseObject.FileName 166 | $Path = Join-Path $ObjectFolder $Filename 167 | $Filter = "Type={0};ID={1}" -f $DatabaseObject.Type,$DatabaseObject.ID 168 | $null = Export-NAVApplicationObject -DatabaseName $DatabaseName -Path $Path -Filter $Filter 169 | } 170 | } 171 | } 172 | 173 | 174 | function Compare-NAVExportObjects 175 | { 176 | [CmdletBinding()] 177 | Param( 178 | [Parameter()] 179 | [System.IO.FileInfo[]]$FileList, 180 | [Parameter()] 181 | [PSCustomObject[]]$DatabaseList 182 | ) 183 | Process 184 | { 185 | $FileBaseNameArray = $FileList.BaseName 186 | $DatabaseListNameArray = $DatabaseList.FileName 187 | $FileObjectSet = [System.Collections.Generic.HashSet[String]]$FileBaseNameArray 188 | $FileObjectSet_dup = [System.Collections.Generic.HashSet[String]]$FileBaseNameArray 189 | $databaseObjectSet = [System.Collections.Generic.HashSet[String]]$DatabaseListNameArray 190 | $FileObjectSet.ExceptWith($databaseObjectSet) 191 | $databaseObjectSet.ExceptWith($FileObjectSet_dup) 192 | 193 | $FileToDelete = $FileList | Where-Object {$FileObjectSet -contains $PSItem.BaseName} 194 | $DatabaseToExport = $DatabaseList | Where-Object {$databaseObjectSet -contains $PSItem.FileName} 195 | 196 | [PSCustomObject]@{ 197 | "FileDeleteList" = $FileToDelete 198 | "DatabaseDeleteList" = $DatabaseToExport 199 | } 200 | } 201 | } 202 | 203 | function Get-NAVDatabaseObjects 204 | { 205 | [CmdletBinding()] 206 | Param( 207 | [Parameter(Mandatory)] 208 | [String]$DatabaseName 209 | ) 210 | Process 211 | { 212 | $Query = "SELECT 213 | [Type], 214 | [ID], 215 | UPPER(SUBSTRING([Type], 1, 3)) + [ID] as [FileName] 216 | FROM 217 | ( 218 | SELECT 219 | CASE [Type] 220 | WHEN 1 THEN 'Table' 221 | WHEN 3 THEN 'Report' 222 | WHEN 5 THEN 'Codeunit' 223 | WHEN 6 THEN 'XMLport' 224 | WHEN 7 THEN 'MenuSuite' 225 | WHEN 8 THEN 'Page' 226 | WHEN 9 THEN 'Query' 227 | END as 'Type', 228 | CAST([ID] as VARCHAR) as [ID] 229 | FROM 230 | [Object] 231 | WHERE 232 | [Type] != 0 AND 233 | [Compiled] = 1 234 | ) as [a] 235 | ORDER BY 236 | [FileName]" 237 | $datarows = Invoke-Sqlcmd -Query $Query -Database $DatabaseName 238 | foreach($row in $datarows){ 239 | [PSCustomObject]@{ 240 | 'Type' = $row.Type 241 | 'ID' = $row.ID 242 | 'FileName' = $row.FileName 243 | } 244 | } 245 | } 246 | } 247 | 248 | Export-ModuleMember -Function "Export-IDENAVObject","Export-FOB" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamics-nav-scm", 3 | "displayName": "Dynamics NAV SCM", 4 | "description": "Source Code Management for Dynamics NAV made easy", 5 | "version": "0.2.0", 6 | "publisher": "cloudreadysoftware", 7 | "author": { 8 | "email": "info@cloud-ready-software.com", 9 | "name": "Cloud Ready Software", 10 | "url": "http://www.cloud-ready-software.com" 11 | }, 12 | "icon": "images/icon.png", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/CloudReadySoftware/DynamicsNAVSCM.git" 16 | }, 17 | "engines": { 18 | "vscode": "^1.12.1" 19 | }, 20 | "categories": [ 21 | "Other" 22 | ], 23 | "activationEvents": [ 24 | "*" 25 | ], 26 | "private": true, 27 | "license": "GPL 3.0", 28 | "main": "./out/src/extension", 29 | "contributes": { 30 | "commands": [ 31 | { 32 | "command": "dynamicsnav.compile_obj", 33 | "title": "Compile objects", 34 | "category": "Dynamics NAV" 35 | }, 36 | { 37 | "command": "dynamicsnav.copy_db", 38 | "title": "Copy database", 39 | "category": "Dynamics NAV" 40 | }, 41 | { 42 | "command": "dynamicsnav.git2nav", 43 | "title": "Git -> NAV", 44 | "category": "Dynamics NAV" 45 | }, 46 | { 47 | "command": "dynamicsnav.nav2git", 48 | "title": "NAV -> Git", 49 | "category": "Dynamics NAV" 50 | }, 51 | { 52 | "command": "dynamicsnav.new_env", 53 | "title": "New development environment", 54 | "category": "Dynamics NAV" 55 | }, 56 | { 57 | "command": "dynamicsnav.remove_env", 58 | "title": "Remove development environment", 59 | "category": "Dynamics NAV" 60 | }, 61 | { 62 | "command": "dynamicsnav.start_ide", 63 | "title": "Start IDE", 64 | "category": "Dynamics NAV" 65 | }, 66 | { 67 | "command": "dynamicsnav.start_rtc", 68 | "title": "Start Windows Client", 69 | "category": "Dynamics NAV" 70 | }, 71 | { 72 | "command": "dynamicsnav.start_shell", 73 | "title": "Start Shell", 74 | "category": "Dynamics NAV" 75 | }, 76 | { 77 | "command": "dynamicsnav.initialize_workspace", 78 | "title": "Initialize Workspace", 79 | "category": "Dynamics NAV" 80 | }, 81 | { 82 | "command": "dynamicsnav.version_objects", 83 | "title": "Start versioning objects", 84 | "category": "Dynamics NAV" 85 | } 86 | ], 87 | "configuration": { 88 | "type": "object", 89 | "title": "NAV Developer Extension config", 90 | "properties": { 91 | "dynamicsnav.zip": { 92 | "type": "string", 93 | "description": "Path to the Zip-file of the current Dynamics NAV solution (Workspace only setting)" 94 | }, 95 | "dynamicsnav.originalobjects": { 96 | "type": "string", 97 | "description": "Path to the originalobjects of the current Dynamics NAV solution (Workspace only setting)" 98 | }, 99 | "dynamicsnav.databasename": { 100 | "type": "string", 101 | "default": null, 102 | "description": "Name of the current database you are working on (Workspace only setting)" 103 | }, 104 | "dynamicsnav.remotedbname": { 105 | "type": "string", 106 | "description": "remotedbname of the current Dynamics NAV solution (Workspace only setting)" 107 | }, 108 | "dynamicsnav.solutionVersion": { 109 | "type": "string", 110 | "description": "Version of the current Dynamics NAV solution (Workspace only setting)" 111 | }, 112 | "dynamicsnav.rtcpath": { 113 | "type": "string", 114 | "description": "Path to the folder which contains the role tailored client (Workspace only setting)" 115 | }, 116 | "dynamicsnav.nstpath": { 117 | "type": "string", 118 | "description": "Path to the folder which contains the Dynamics NAV service tier (Workspace only setting)" 119 | }, 120 | "dynamicsnav.sourcepath": { 121 | "type": "string", 122 | "default": null, 123 | "description": "Source path" 124 | }, 125 | "dynamicsnav.targetpath": { 126 | "type": "string", 127 | "default": null, 128 | "description": "Target path" 129 | }, 130 | "dynamicsnav.resultpath": { 131 | "type": "string", 132 | "default": null, 133 | "description": "Resulting path" 134 | }, 135 | "dynamicsnav.versions": { 136 | "type": "array", 137 | "default": null, 138 | "description": "Versions object" 139 | }, 140 | "dynamicsnav.installpath": { 141 | "type": "string", 142 | "default": "C:\\Program Files\\Microsoft Dynamics NAV\\", 143 | "description": "The install path for NST/RTC" 144 | }, 145 | "dynamicsnav.remotedbinstance": { 146 | "type": "string", 147 | "default": null, 148 | "description": "Remote database instance" 149 | }, 150 | "dynamicsnav.licensefile": { 151 | "type": "string", 152 | "default": null, 153 | "description": "Location of the licensefile" 154 | }, 155 | "dynamicsnav.commonsqllocation": { 156 | "type": "string", 157 | "default": null, 158 | "description": "SQL backupfile location" 159 | }, 160 | "dynamicsnav.uidoffset": { 161 | "type": "integer", 162 | "default": 50000, 163 | "description": "The UidOffset in the Dynamics NAV solution" 164 | }, 165 | "dynamicsnav.defaultNAVBuildFolder": { 166 | "type": "string", 167 | "default": "", 168 | "description": "Default path to Navision versions" 169 | }, 170 | "dynamicsnav.filters": { 171 | "type": "array", 172 | "description": "Filter to export" 173 | }, 174 | "dynamicsnav.datemodification": { 175 | "type": "boolean", 176 | "default": true, 177 | "description": "Modify the date to match the original object" 178 | } 179 | } 180 | } 181 | }, 182 | "scripts": { 183 | "vscode:prepublish": "npm run build", 184 | "clean:delete": "rimraf out", 185 | "clean:create": "mkdirp out/src", 186 | "build": "npm run clean:delete && npm run clean:create && npm run build:powershell && npm run compile:typescript", 187 | "build:powershell": "ncp src/powershell out/src/powershell/", 188 | "compile:typescript": "tsc -p ./", 189 | "postinstall": "node ./node_modules/vscode/bin/install" 190 | }, 191 | "dependencies": { 192 | "powershell": "^2.2.0", 193 | "format-duration": "^1.0.0", 194 | "path": "^0.12.7", 195 | "vscode": "^1.1.0", 196 | "time-stamp": "^1.0.1" 197 | }, 198 | "devDependencies": { 199 | "@types/node": "^7.0.8", 200 | "ncp": "^2.0.0", 201 | "mkdirp": "^0.5.1", 202 | "rimraf": "^2.6.1", 203 | "typescript": "^2.2.1", 204 | "vsce": "^1.18.0" 205 | } 206 | } -------------------------------------------------------------------------------- /src/powershell/modules/new_nav.psm1: -------------------------------------------------------------------------------- 1 | 2 | function Import-NAVLicense 3 | { 4 | [CmdletBinding()] 5 | Param( 6 | [Parameter(Mandatory)] 7 | [String]$NAVLicenseFile, 8 | [Parameter(Mandatory)] 9 | [String]$ServiceInstanceName 10 | ) 11 | Process 12 | { 13 | if(Test-Path $NAVLicenseFile -PathType Leaf) 14 | { 15 | $licensefile = Get-Content -Path $NAVLicenseFile -Encoding Byte 16 | Import-NAVServerLicense $ServiceInstanceName -LicenseData $licensefile -Database NavDatabase -WarningAction SilentlyContinue 17 | } 18 | } 19 | } 20 | 21 | 22 | function Set-UIDOffset 23 | { 24 | [CmdletBinding()] 25 | Param( 26 | [Parameter()] 27 | [Int]$uidOffset, 28 | [Parameter(Mandatory)] 29 | [String]$DatabaseName 30 | ) 31 | Process 32 | { 33 | if($uidOffset -eq 0) 34 | { 35 | return 36 | } 37 | $SQLString = 'UPDATE [$ndo$dbproperty] SET [uidoffset] = {0}' -f $uidOffset 38 | Invoke-Sqlcmd -Query $SQLString -Database $DatabaseName 39 | } 40 | } 41 | 42 | function Expand-NAVArchive 43 | { 44 | [CmdletBinding()] 45 | Param( 46 | [Parameter(Mandatory)] 47 | [String]$ZipFile, 48 | [Parameter(Mandatory)] 49 | [String]$NSTFolder, 50 | [Parameter(Mandatory)] 51 | [String]$RTCFolder, 52 | [Parameter(Mandatory)] 53 | [bool]$ServiceTier, 54 | [Parameter(Mandatory)] 55 | [bool]$RoleTailoredClient 56 | ) 57 | Process 58 | { 59 | $tempfolder = Get-TempFolder 60 | $ZipFileName = Copy-Item -Path $ZipFile -Destination $tempfolder -PassThru 61 | $ExtractedNAVFiles = Join-Path $tempfolder "NAV" 62 | $null = New-Item -ItemType Directory $ExtractedNAVFiles 63 | Expand-Archive $ZipFileName $ExtractedNAVFiles -Verbose:$false 64 | $NAVVersionFolder = Join-Path $ExtractedNAVFiles 'RoleTailoredClient\program files\Microsoft Dynamics NAV\' 65 | $NAVVersion = Get-ChildItem -Directory -Path $NAVVersionFolder | Select-Object -First 1 -ExpandProperty "Name" 66 | if ($ServiceTier) { 67 | Copy-ServiceTier -ExtractedNAVFiles $ExtractedNAVFiles -NSTFolder $NSTFolder -NAVVersion $NAVVersion 68 | } 69 | if ($RoleTailoredClient) { 70 | Copy-RoleTailoredClient -ExtractedNAVFiles $ExtractedNAVFiles -RTCFolder $RTCFolder -NAVVersion $NAVVersion 71 | } 72 | Remove-Item $tempfolder -Recurse -Force -ErrorAction 'SilentlyContinue' 73 | } 74 | } 75 | 76 | function Copy-ServiceTier 77 | { 78 | [CmdletBinding()] 79 | Param( 80 | [Parameter(Mandatory)] 81 | [String]$ExtractedNAVFiles, 82 | [Parameter(Mandatory)] 83 | [String]$NSTFolder, 84 | [Parameter(Mandatory)] 85 | [String]$NAVVersion 86 | ) 87 | Process 88 | { 89 | 90 | if(-not (Test-Path $NSTFolder)) 91 | { 92 | $null = New-Item -Path $NSTFolder -ItemType Directory 93 | } 94 | $BaseFiles = Join-Path $ExtractedNAVFiles "ServiceTier\program files\Microsoft Dynamics NAV\$NAVVersion\Service\*" 95 | $LanguageFiles = Join-Path $ExtractedNAVFiles "Installers\*\Server\PFiles\Microsoft Dynamics NAV\$NAVVersion\Service\*" 96 | Copy-Item -Path $BaseFiles -Destination $NSTFolder -Recurse 97 | Copy-Item -Path $LanguageFiles -Destination $NSTFolder -Recurse -Force 98 | } 99 | } 100 | 101 | function Copy-RoleTailoredClient 102 | { 103 | [CmdletBinding()] 104 | Param( 105 | [Parameter(Mandatory)] 106 | [String]$ExtractedNAVFiles, 107 | [Parameter(Mandatory)] 108 | [String]$RTCFolder, 109 | [Parameter(Mandatory)] 110 | [String]$NAVVersion 111 | ) 112 | Process 113 | { 114 | if(-not (Test-Path $RTCFolder)) 115 | { 116 | $null = New-Item -Path $RTCFolder -ItemType Directory 117 | } 118 | $BaseFiles = Join-Path $ExtractedNAVFiles "RoleTailoredClient\program files\Microsoft Dynamics NAV\$NAVVersion\RoleTailored Client\*" 119 | $LanguageFiles = Join-Path $ExtractedNAVFiles "Installers\*\RTC\PFiles\Microsoft Dynamics NAV\$NAVVersion\RoleTailored Client\*" 120 | Copy-Item -Path $BaseFiles -Destination $RTCFolder -Recurse 121 | Copy-Item -Path $LanguageFiles -Destination $RTCFolder -Recurse -Force 122 | Copy-UserSettings -ExtractedNAVFiles $ExtractedNAVFiles -NAVVersion $NAVVersion 123 | } 124 | } 125 | 126 | function Copy-UserSettings 127 | { 128 | [CmdletBinding()] 129 | Param( 130 | [Parameter(Mandatory)] 131 | [String]$ExtractedNAVFiles, 132 | [Parameter(Mandatory)] 133 | [String]$NAVVersion 134 | ) 135 | Process 136 | { 137 | $UserSettings = Join-Path $env:appdata "Microsoft\Microsoft Dynamics NAV\$NAVVersion\ClientUserSettings.config" 138 | if (Test-Path $UserSettings) 139 | { 140 | return 141 | } 142 | $UserSettingsFolder = Split-Path $UserSettings 143 | if(-not (Test-Path $UserSettingsFolder)) 144 | { 145 | $null = New-Item -ItemType Directory $UserSettingsFolder 146 | } 147 | $DefaultUserSettings = Join-Path $ExtractedNAVFiles "RoleTailoredClient\CommonAppData\Microsoft\Microsoft Dynamics NAV\$NAVVersion\ClientUserSettings.config" 148 | Copy-Item -Path $DefaultUserSettings -Destination $UserSettings 149 | } 150 | } 151 | 152 | 153 | function Assert-AdminProcess 154 | { 155 | [CmdletBinding()] 156 | Param( 157 | ) 158 | Process 159 | { 160 | $currentUser = [Security.Principal.WindowsPrincipal]([Security.Principal.WindowsIdentity]::GetCurrent()) 161 | $isAdminProcess = $currentUser.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 162 | if(-not $isAdminProcess) 163 | { 164 | throw "The process cannot be completed because it isnt running as admin." 165 | } 166 | } 167 | } 168 | 169 | function Copy-NAVConfig { 170 | [CmdletBinding()] 171 | Param( 172 | [Parameter(Mandatory)] 173 | [String]$ServiceFolder, 174 | [Parameter(Mandatory)] 175 | [String]$DatabaseName, 176 | [Parameter(Mandatory)] 177 | [String]$ServiceInstanceName, 178 | [Parameter(Mandatory)] 179 | [String]$ConfigLocation 180 | ) 181 | Process 182 | { 183 | $ConfigFolder = Join-Path $ConfigLocation $ServiceInstanceName 184 | $null = New-Item -Type Directory $ConfigFolder 185 | $XmlFile = Join-Path $ServiceFolder "CustomSettings.config" 186 | [xml]$configXML = Get-Content $XmlFile 187 | $configXML.appSettings.add | Where-Object {$_.key -eq "DatabaseName"} | ForEach-Object {$_.value = $DatabaseName} 188 | $configXML.appSettings.add | Where-Object {$_.key -eq "ServerInstance"} | ForEach-Object {$_.value = $ServiceInstanceName} 189 | $configFileName = Join-Path $ConfigFolder "CustomSettings.config" 190 | $configXML.Save($configFileName) 191 | $tenantsConfig = Join-Path $ServiceFolder "Tenants.config" 192 | if(Test-Path $tenantsConfig){ 193 | Copy-Item $tenantsConfig $ConfigFolder 194 | } 195 | $configFile = Join-Path $ServiceFolder "Microsoft.Dynamics.Nav.Server.exe.config" 196 | $configFileName = Join-Path $ConfigFolder "$ServiceInstanceName.config" 197 | Copy-Item $ConfigFile $configFilename -PassThru 198 | } 199 | } 200 | 201 | function Test-NAVInstanceWindows10 202 | { 203 | [CmdletBinding()] 204 | Param( 205 | [Parameter(Mandatory)] 206 | [String]$NAVServiceInstance 207 | ) 208 | Process 209 | { 210 | if ([Environment]::OSVersion.Version.Major -ne 10) 211 | { 212 | return 213 | } 214 | $module = Get-Module -Name "Microsoft.Dynamics.Nav.Management" 215 | if(!$module) 216 | { 217 | return 218 | } 219 | $moduleItem = Get-Item $module.Path 220 | $versionInfo = $moduleItem.VersionInfo 221 | if(($versionInfo.ProductMajorPart -le 8) -and ($versionInfo.ProductBuildPart -le 41779)) 222 | { 223 | Invoke-NAVCodeunit -CodeunitId 1 -ServerInstance $NAVServiceInstance -ErrorAction SilentlyContinue 224 | } 225 | } 226 | } 227 | 228 | 229 | function Install-NAVAddIns 230 | { 231 | [CmdletBinding()] 232 | Param( 233 | [Parameter(Mandatory)] 234 | [String]$SourceAddinsFolder, 235 | [Parameter(Mandatory)] 236 | [String]$RTCAddinsFolder, 237 | [Parameter(Mandatory)] 238 | [String]$NSTAddinsFolder 239 | ) 240 | Process 241 | { 242 | if(-not (Test-Path $SourceAddinsFolder)) { 243 | return 244 | } 245 | $AddIns = Get-ChildItem $SourceAddinsFolder 246 | if(-not $AddIns) 247 | { 248 | return 249 | } 250 | $AddInFiles = $AddIns.FullName 251 | if($NSTAddinsFolder) 252 | { 253 | $null = New-Item -ItemType Directory -Force -Path $NSTAddinsFolder 254 | Copy-Item $AddInFiles $NSTAddinsFolder -Force 255 | } 256 | $null = New-Item -ItemType Directory -Force -Path $RTCAddinsFolder 257 | Copy-Item $AddInFiles $RTCAddinsFolder -Force 258 | } 259 | } 260 | 261 | function New-NAVServiceTier 262 | { 263 | [CmdletBinding()] 264 | Param( 265 | [Parameter(Mandatory)] 266 | [String]$NSTFolder, 267 | [Parameter(Mandatory)] 268 | [String]$DatabaseName, 269 | [Parameter(Mandatory)] 270 | [String]$ServiceInstanceName, 271 | [Parameter(Mandatory)] 272 | [String]$NSTEXE 273 | ) 274 | Process 275 | { 276 | $ConfigLocation = Join-Path $NSTFolder "Instances" 277 | $ConfigFile = Copy-NAVConfig -ServiceFolder $NSTFolder -DatabaseName $DatabaseName -ServiceInstanceName $ServiceInstanceName -ConfigLocation $ConfigLocation 278 | $binPath = "`"{1}`" `${0} config `"{2}`"" -f $ServiceInstanceName, $NSTEXE, $ConfigFile 279 | $Dependencies = "HTTP","NetTcpPortSharing" 280 | $DisplayName = "Microsoft Dynamics NAV Server [{0}]" -f $ServiceInstanceName 281 | $Description = "Service handling requests to Microsoft Dynamics NAV application." 282 | $ServiceName = 'MicrosoftDynamicsNavServer${0}' -f $ServiceInstanceName 283 | $null = New-Service -Name $ServiceName -BinaryPathName $binPath -DisplayName $DisplayName -Description $Description -StartupType Automatic -DependsOn $Dependencies 284 | } 285 | } 286 | 287 | function Set-ServiceCredential 288 | { 289 | [CmdletBinding()] 290 | param( 291 | [Parameter(Mandatory)] 292 | [String]$ServiceInstanceName 293 | ) 294 | $ServiceCredential = Get-Credential -Message "Credentials for '$ServiceInstanceName'" -UserName $(whoami) 295 | $filter = 'Name = "MicrosoftDynamicsNavServer${0}"' -f $ServiceInstanceName 296 | $services = Get-WmiObject -Class "Win32_Service" -Filter $filter 297 | if($services) 298 | { 299 | $MultipleServices = [bool](Get-Member -InputObject $services -Name 'Count' -MemberType Properties) 300 | } 301 | if ((-not $services) -or ($MultipleServices)) { 302 | throw "Unable to find services named '$ServiceInstanceName'." 303 | } 304 | Set-ServiceCredentials -Service $services -ServiceCredential $ServiceCredential 305 | } 306 | 307 | function Set-ServiceCredentials 308 | { 309 | [CmdletBinding()] 310 | param( 311 | [Parameter(Mandatory,ValueFromPipeline)] 312 | [System.Management.ManagementObject[]] $Service, 313 | [Parameter(Mandatory)] 314 | [Management.Automation.PSCredential] $ServiceCredential 315 | ) 316 | process { 317 | $changeService = $Service.Change($null, # DisplayName 318 | $null, # PathName 319 | $null, # ServiceType 320 | $null, # ErrorControl 321 | $null, # StartMode 322 | $null, # DesktopInteract 323 | $serviceCredential.UserName, # StartName 324 | $serviceCredential.GetNetworkCredential().Password, # StartPassword 325 | $null, # LoadOrderGroup 326 | $null, # LoadOrderGroupDependencies 327 | $null) # ServiceDependencies 328 | $returnValue = $changeService.ReturnValue 329 | $errorMessage = "Error setting credentials for service '$serviceName'" 330 | switch ( $returnValue ) { 331 | 0 { Write-Verbose "Set credentials for service '$serviceName'" } 332 | 1 { Write-Error "$errorMessage - Not Supported" } 333 | 2 { Write-Error "$errorMessage - Access Denied" } 334 | 3 { Write-Error "$errorMessage - Dependent Services Running" } 335 | 4 { Write-Error "$errorMessage - Invalid Service Control" } 336 | 5 { Write-Error "$errorMessage - Service Cannot Accept Control" } 337 | 6 { Write-Error "$errorMessage - Service Not Active" } 338 | 7 { Write-Error "$errorMessage - Service Request timeout" } 339 | 8 { Write-Error "$errorMessage - Unknown Failure" } 340 | 9 { Write-Error "$errorMessage - Path Not Found" } 341 | 10 { Write-Error "$errorMessage - Service Already Stopped" } 342 | 11 { Write-Error "$errorMessage - Service Database Locked" } 343 | 12 { Write-Error "$errorMessage - Service Dependency Deleted" } 344 | 13 { Write-Error "$errorMessage - Service Dependency Failure" } 345 | 14 { Write-Error "$errorMessage - Service Disabled" } 346 | 15 { Write-Error "$errorMessage - Service Logon Failed" } 347 | 16 { Write-Error "$errorMessage - Service Marked For Deletion" } 348 | 17 { Write-Error "$errorMessage - Service No Thread" } 349 | 18 { Write-Error "$errorMessage - Status Circular Dependency" } 350 | 19 { Write-Error "$errorMessage - Status Duplicate Name" } 351 | 20 { Write-Error "$errorMessage - Status Invalid Name" } 352 | 21 { Write-Error "$errorMessage - Status Invalid Parameter" } 353 | 22 { Write-Error "$errorMessage - Status Invalid Service Account" } 354 | 23 { Write-Error "$errorMessage - Status Service Exists" } 355 | 24 { Write-Error "$errorMessage - Service Already Paused" } 356 | } 357 | } 358 | } --------------------------------------------------------------------------------