Cent and Yen in Dynamics NAV 2018

Microsoft Dynamics NAV logo

If you run a Danish Dynamics NAV and get objects from outside Denmark, you might experience the Danish letter ø/Ø beeing replaced by ¢/¥ (the sign for Cent and Yen).

The fix?
Simply export all your objects as text, run them through the following powershell and import them again.

(((Get-Content -encoding Oem "InFile.txt") -replace ([char]8250),([char]251)) -replace ([char]157),([char]238)) | Set-Content -encoding Oem "OutFile.txt"

 

Finish off by a compile and syncronize all tables.

This is off cause on your own risk, so start by taking a full backup of your NAV system/server.


2018-12-20 Updated: powershell improved and now using unicode values of chars

My Dynamics NAV App is FUBAR (updated 2018-06-22 with new solution)

Microsoft Dynamics NAV logo

For readers not knowing what FUBAR is, please see Wikipedia.

If you Dynamics NAV 2018 service will not start (or actually shuts down immediately when you try to start it), and you get something like this in your Windows Event Viewer (App Name, object ID and/or Name can probably be different ones):

Message: <ii>An error occurred while applying changes from the 'Payment and Reconciliation Formats (DK) by Microsoft 1.0.20348.0' app to the application object of type 'PageExtension' with the ID '13623'. The error was: InvalidOperationException - Metadata delta application failed due to the following error(s): The metadata object IncomingDocAttachFile was not found.</ii>

…your installation is FUBAR (or at least I don’t know how to repair it easily).

PowerShell cannot uninstall/unpublish the App when no Service Tier is running – so that is not an option.

Also I was not able to Google a solution, but here is what I did to make the service tier run again:

  • Take a fresh backup, and make sure nobody will open NAV until you say OK (it will be possible as soon as we get the Service Tier running again).
  • Export all your objects to text and locate (search for) the (probably page) object with the metadata object that could not be found (i.e. “IncomingDocAttachFile” in my example). I found it in Page Object 25.
  • Export the (Page) Object to a FOB-file (just so you have it).
    Overwrite/reimport the (Page) Object from a backup without your latest changes (be warned – if this is a table object, this will empty the table – so make sure to preserve the data somehow).
  • Redo your changes to the object without messing with the metadata object that could not be found (i.e. “IncomingDocAttachFile” in my example.

Test that everything is ok, before letting anyone work again.

NOTE: I’ve not tried this with a table object, only a page object – so I’m not even sure this error can occur on tables?

DISCLAIMER: This is probably unsupported by Microsoft and you should do this on your own risk. We will not be responsible for any problems, data loss or down time etc. you may discover/experience following this procedure, or after doing this procedure. You are on your own here – but hopefully this will help you and save you the effort of finding a solution yourself! 🙂


2018-06-22 I did’nt quite nail the solution first time around, as the problem creped in again. So I change the solution to the right one 🙂

“SQL Server does not exist or access denied” using Windows 10, build 1803

Microsoft SQL Server logo

If you are running your Microsoft Dynamics C5 or Microsoft Concorde XAL program files off a SMB1.x share, and your client is running Microsoft Windows 10, your C5/XAL might stop working if you upgrade your Windows 10 to build 1803.

This is really a “far out” bug, in build 1803 Microsoft apparently strengthened some security which makes it impossible to connect to a SQL Server through ODBC if – and only if – your program files are stored on a file sharing running SMB1.x (which means it will actually work if you copy your program files to a local drive – but that will off cause break things – some C5 and XAL files needs to be shared!).

And now we’re at it – this is not only hitting C5 and XAL, but other systems using ODBC as well: https://www.google.com/search?source=hp&ei=zfH6WqasBorGwQKImqroDw&q=sql+connection+problem+after+windows+update+1803&oq=sql+connection+problem+after+windows+update+1803

The solution is straight forward: make your file share run SMB2.0 or newer:

Hint: If you run this PowerShell command on your client, it will list all your network shares and the SMB-version in use for each of them:

Get-SmbConnection

Even Microsoft gets this wrong :-)

Microsoft Dynamics NAV logo

In Dynamics NAV events are also fired on temporary records – this is really a confusing design and can trigger various, hard to debug, but rather serious issues.

On one customer running NAV 2018 CU1, user group memberships was cleared randomly. We did’nt know exactly when, but in the end one of my Indian colleagues figured out it happened related to the “Export to a datafile” functionality.
I was pretty sure someone was doing a DELETEALL – but was it in our code or in the standard code? And exactly how/where?

Actually it was easily found.
I created a subscriber one the delete trigger in table 9001 / User Group Member – only with one line of code:

ERROR('STOP');

…and started the debugger.

Note that events are fired even if the table trigger is not executed (i.e. INSERT/DELETE/MODIFY/RENAME is called with FALSE).

The deletion of the group membership is caused by the select/deselect all companies on the “Export to a Datafile” page in NAV. The list of companies is build in a temporary company record and DELETEALL is used on it when you select or deselect the all companies flag.

Unfortunately there is a subscriber in Codeunit 2 called OnAfterCompanyDeleteRemoveReferences which does not check if it is actually called with a company beeing removed, or just called with a temporary record instance from somewhere.
So it cleans up anyway – removing actual reference data from still existing companies!

This shows very clear that non-self-explanatory functionality like events beeing called on temporary records should have been avoided by Microsoft when designing this.
After all the goal of the development environment and -language should be to help developers write correct code – not to trap them into creating bugs.

If I had to redesign this functionality I would make it an function trigger property (default off) if the subscriber should run on temporary record instances.
Or at least let a subscriber parameter tell if it is temporary or not (not that this would make it easier to check, but it would remind you about checking it every time you created a subscriber 🙂 ).

To me the current functionality is wrong, people are getting bitten by this. It is illogical design and even if you tried it once or even multiple times (and my guess is that you have if you are writing event subscribers), you’ll probably forget it and do it again in the future.

At least we know we are not alone – Microsoft C\AL developers are actually also getting bitten by this :-).

Please note – this bug is not only emptying the 9001 / User Group Member table, but other tables as well: User Group Access Control, Application Area Setup, Custom Report Layout and Repor tLayout Selection.

By the way: The correct fix (introduced in NAV 2018 CU2) is to make these lines the first two lines in Codeunit 2, OnAfterCompanyDeleteRemoveReferences:

IF Rec.ISTEMPORARY THEN
  EXIT;
[…]

Sendmail/smart host with mysmtp.eu/smtp.dk

FreeBSD logo

Hi,

My internet service provider (ISP, Eniig) has announced that they will no longer provide a SMTP relay host (outgoing Simple Mail Transfer Protocol relay), and on top of that they earlier they stated that they will not setup reverse DNS on my fixed IP – so with these two (really bad) decisions in mind, I had to come up with a solution.

By the way – Eniigs reason for not providing SMTP relay servides anymore is that they apparently cannot manage to have such a server in production anymore because of SPAM anti measures etc. Really? An ISP not having the resources and knowledge to run a SMTP server in production anymore? Really!?!?!

Anyway – Eniig suggested using https://smtp.dk/ and even provides a free (almost unlimited) access until end of May 2018, so although there is a lot of cheaper alternatives out there – why not?
For non-Danish speakers wanting to join, smtp.dk also runs an international version of their service on https://mysmtp.eu/.

Only problem is that it requires SMTP to the submit port (587) and authentication. And it is a requirement for me to continue to use my FreeBSD/Sendmail box, which is providing a lot of services – including some rarely used SMTP-services – for family and friends. This is actually a non-profit/free setup – so it is a pita that Eniig actually are putting extra costs into this, without reducing the cost for the Internet connection. Shame on them!!!

Actually, it was not that big of a hassle to configure Sendmail for this purpose – and I’m also doing SMTP authentication, SpamAssassin filtering, procmail  and a lot of other stuff in my Sendmail :-).

This is what I had to do:

1.
Create /etc/mail/authinfo with one line (terminate it with a line break):

AuthInfo:smtp.dk "U:root" "I:<smtp.dk login name>" "P:<smtp.dk password>" "M:LOGIN PLAIN"

…replacing values in < > with you accounts values from smtp.dk
I feel most secure by making this file rw for root only

2.
Run makemap of this file to create a authinfo.db-file

makemap hash authinfo < authinfo

3.
Correct your sendmail.mc-file (whatever it is called on your system). Add these lines:

define(`SMART_HOST',`[smtp.dk]')dnl
define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl
FEATURE(`authinfo',`hash /etc/mail/authinfo')dnl

Note: You should change this for the sendmail queue runner process, not a SMTP submit service etc.

4.
Compile your sendmail.mc file into a sendmail.cf file. This is easily done on FreeBSD 🙂

make cf

5.
Restart sendmail. Again – this is easy on FreeBSD

make restart

Also: remember to correct your SPF and similar setups on your domains according to smtp.dk/mysmtp.eu.

Annoying non-rotating picture frame

Big Ben upside-down

If you got a cheap Chinese Picture Frame, you have probably noticed that half your pictures are upside-down, or rotated left or right.

It happens even if the pictures are shown correctly on your computer, so if you got loads of pictures on your frame, it can be quite a hassle to figure out which files are orientated wrong.

The cause of this is the EXIF attribute which stores the correct orientation of the picture. Or rather that most Picture Frames ignores this attribute, probably because they do not have the CPU power to rotate the picture anyway.

The easiest fix is to run this PowerShell script on all your pictures – it will read the EXIF attribute and actually rotate the picture so your Picture frame does not have to.

Have fun!


2020-04-11: Updated: Now the script also resizes images (while maintaining aspect ratio) so your huge images won’t fill up your picture frame (remember to keep a copy of the images if you need them in full resolution)

# 20200411 Gert Lynge. Source: http://www.dabbler.dk
#
[System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $null

function Get-EXIFRotationAttribute($FilePath) {
# Returns:
# 1 = "Horizontal"
# 3 = "Rotate 180 degrees"
# 6 = "Rotate 90 degrees clockwise"
# 8 = "Rotate 270 degrees clockwise"
  $WiaImage = New-Object -ComObject Wia.ImageFile
  $WiaImage.LoadFile($FilePath)
  if($WiaImage.Properties.Exists("274")) {
    return ($WiaImage.Properties.Item("274").Value)
  }
  return -1
}

function RotateClockwise-JpegImage($Degrees,$FilePath) {
  $Image = [System.Drawing.image]::FromFile( $FilePath )
  switch($Degrees) {
    90  { $Image.rotateflip("Rotate90FlipNone") }
    180 { $Image.rotateflip("Rotate180FlipNone") }
    270 { $Image.rotateflip("Rotate270FlipNone") }
  }
  $Image.save($FilePath)
  $Image.Dispose()
  Write-Host -ForegroundColor Yellow "$FilePath rotated $Degrees degrees clockwise"
}

function ResizeImage() {
    param([String]$ImagePath, [Int]$Quality = 90, [Int]$targetSize, [String]$OutputLocation)
 
    Add-Type -AssemblyName "System.Drawing"
 
    $img = [System.Drawing.Image]::FromFile($ImagePath)
 
    $CanvasWidth = $targetSize
    $CanvasHeight = $targetSize
 
    #Encoder parameter for image quality
    $ImageEncoder = [System.Drawing.Imaging.Encoder]::Quality
    $encoderParams = New-Object System.Drawing.Imaging.EncoderParameters(1)
    $encoderParams.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter($ImageEncoder, $Quality)
 
    # get codec
    $Codec = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where {$_.MimeType -eq 'image/jpeg'}
 
    #compute the final ratio to use
    $ratioX = $CanvasWidth / $img.Width;
    $ratioY = $CanvasHeight / $img.Height;
 
    $ratio = $ratioY
    if ($ratioX -le $ratioY) {
        $ratio = $ratioX
    }

    if ($ratio -ne 1) { 
      $newWidth = [int] ($img.Width * $ratio)
      $newHeight = [int] ($img.Height * $ratio)
 
      $bmpResized = New-Object System.Drawing.Bitmap($newWidth, $newHeight)
      $graph = [System.Drawing.Graphics]::FromImage($bmpResized)
      $graph.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
 
      $graph.Clear([System.Drawing.Color]::White)
      $graph.DrawImage($img, 0, 0, $newWidth, $newHeight)
 
      #save to file
      $img.Dispose()
      $bmpResized.Save($OutputLocation, $Codec, $($encoderParams))
      $bmpResized.Dispose()
      
      Write-Host -ForegroundColor Yellow "$FilePath scaled to $newWidth * $newHeight"
    }
}

function Rotate-JpegImages($ImagePath) {
  write-host -ForegroundColor Yellow "Checking for jpg/jpeg images that needs rotation in path $ImagePath (including subdirectories)..."
  $JpegFiles = get-childitem -Recurse -Path "$ImagePath\*" -File -Include @("*.jpg","*.jpeg")
  ForEach($JpegFile IN $JpegFiles) {
    $FilePath = Join-Path -Path $ImagePath -ChildPath $JpegFile.Name

    $EXIFRotationAttribute = Get-EXIFRotationAttribute $FilePath
    switch($EXIFRotationAttribute) {
      6 { RotateClockwise-JpegImage 90 $FilePath }
      3 { RotateClockwise-JpegImage 180 $FilePath }
      8 { RotateClockwise-JpegImage 270 $FilePath }
    }

    ResizeImage -ImagePath $FilePath -targetSize 1280 -OutputLocation $FilePath

  }
}

# Rotate all images on D: (including files in subdirectories)
clear
Rotate-JpegImages "D:\"