How to Detect any .NET Framework: Difference between revisions

From NSIS Wiki
Jump to navigationJump to search
(Add > 4.5 support.)
Line 12: Line 12:
== The Abort If Bad Function ==
== The Abort If Bad Function ==
<highlight-nsis>
<highlight-nsis>
;;USAGE:
;; USAGE:
!define MIN_FRA_MAJOR "3"
!define MIN_FRA_MAJOR "3"
!define MIN_FRA_MINOR "0"
!define MIN_FRA_MINOR "0"
!define MIN_FRA_BUILD "*"
!define MIN_FRA_BUILD "*"
;
;
;;NB Use an asterisk to match anything.
;; NB Use an asterisk to match anything.
;Call AbortIfBadFramework
; Call AbortIfBadFramework
;; No pops. It just aborts inside the function, or returns if all is well.
;; No pops. It just aborts inside the function, or returns if all is well.
;; Change this if you like.
;; Change this if you like.
Function AbortIfBadFramework
Function AbortIfBadFramework


   ;Save the variables in case something else is using them
   ; Save the variables in case something else is using them
   Push $0
   Push $0
   Push $1
   Push $1
Line 38: Line 38:
   Push $R8
   Push $R8


  ; Major
   StrCpy $R5 "0"
   StrCpy $R5 "0"
  ; Minor
   StrCpy $R6 "0"
   StrCpy $R6 "0"
  ; Build
   StrCpy $R7 "0"
   StrCpy $R7 "0"
  ; No Framework
   StrCpy $R8 "0.0.0"
   StrCpy $R8 "0.0.0"
   StrCpy $0 0
   StrCpy $0 0


   loop:
   loop:


   ;Get each sub key under "SOFTWARE\Microsoft\NET Framework Setup\NDP"
   ; Get each sub key under "SOFTWARE\Microsoft\NET Framework Setup\NDP"
   EnumRegKey $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP" $0
   EnumRegKey $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP" $0
   StrCmp $1 "" done ;jump to end if no more registry keys
   StrCmp $1 "" done ;jump to end if no more registry keys
Line 53: Line 61:
   StrCpy $3 $1 "" 1 ;Remainder of string
   StrCpy $3 $1 "" 1 ;Remainder of string


   ;Loop if first character is not a 'v'
   ; Loop if first character is not a 'v'
   StrCmpS $2 "v" start_parse loop
   StrCmpS $2 "v" start_parse loop


   ;Parse the string
   ; Parse the string
   start_parse:
   start_parse:
   StrCpy $R1 ""
   StrCpy $R1 ""
Line 66: Line 74:


   parse:
   parse:
   StrCmp $3 "" parse_done ;If string is empty, we are finished
   StrCmp $3 "" parse_done ; If string is empty, we are finished
   StrCpy $2 $3 1 ;Cut off the first character
   StrCpy $2 $3 1 ; Cut off the first character
   StrCpy $3 $3 "" 1 ;Remainder of string
   StrCpy $3 $3 "" 1 ; Remainder of string
   StrCmp $2 "." is_dot not_dot ;Move to next part if it's a dot
   StrCmp $2 "." is_dot not_dot ; Move to next part if it's a dot


   is_dot:
   is_dot:
   IntOp $4 $4 + 1 ; Move to the next section
   IntOp $4 $4 + 1 ; Move to the next section
   goto parse ;Carry on parsing
   goto parse ; Carry on parsing


   not_dot:
   not_dot:
Line 83: Line 91:
   major_ver:
   major_ver:
   StrCpy $R1 $R1$2
   StrCpy $R1 $R1$2
   goto parse ;Carry on parsing
   goto parse ; Carry on parsing


   minor_ver:
   minor_ver:
   StrCpy $R2 $R2$2
   StrCpy $R2 $R2$2
   goto parse ;Carry on parsing
   goto parse ; Carry on parsing


   build_ver:
   build_ver:
   StrCpy $R3 $R3$2
   StrCpy $R3 $R3$2
   goto parse ;Carry on parsing
   goto parse ; Carry on parsing


   parse_done:
   parse_done:
Line 121: Line 129:
   done:
   done:


   ;Have we got the framework we need?
   ; Have we got the framework we need?
   IntCmp $R5 ${MIN_FRA_MAJOR} max_major_same fail end
   IntCmp $R5 ${MIN_FRA_MAJOR} max_major_same fail end
   max_major_same:
   max_major_same:
  ; Versions > 4.5 have different logic.
  IntCmp ${MIN_FRA_MAJOR} 4 0 before45 end
  IntCmp ${MIN_FRA_MINOR} 5 max_major_greaterthan45 before45 max_major_greaterthan45
  before45:
   IntCmp $R6 ${MIN_FRA_MINOR} max_minor_same fail end
   IntCmp $R6 ${MIN_FRA_MINOR} max_minor_same fail end
   max_minor_same:
   max_minor_same:
   IntCmp $R7 ${MIN_FRA_BUILD} end fail end
   IntCmp $R7 ${MIN_FRA_BUILD} end fail end
  max_major_greaterthan45:
  !if ${MIN_FRA_MINOR} == "5"
  !if ${MIN_FRA_BUILD} == "0"
    !define MIN_FRA_RELEASE "378389"
  !else if ${MIN_FRA_BUILD} == "1"
    !define MIN_FRA_RELEASE "378675"
  !else if ${MIN_FRA_BUILD} == "2"
    !define MIN_FRA_RELEASE "379893"
  !else
    !error "AbortIfBadFramework was passed a bad framework version ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD}."
  !endif
  !else if ${MIN_FRA_MINOR} == "6"
  !if ${MIN_FRA_BUILD} == "0"
    !define MIN_FRA_RELEASE "393295"
  !else if ${MIN_FRA_BUILD} == "1"
    !define MIN_FRA_RELEASE "394254"
  !else if ${MIN_FRA_BUILD} == "2"
    !define MIN_FRA_RELEASE "394802"
  !else
    !error "AbortIfBadFramework was passed a bad framework version ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD}."
  !endif
  !else if ${MIN_FRA_MINOR} == "7"
  !if ${MIN_FRA_BUILD} == "0"
    !define MIN_FRA_RELEASE "460798"
  !else if ${MIN_FRA_BUILD} == "1"
    !define MIN_FRA_RELEASE "461308"
  !else if ${MIN_FRA_BUILD} == "2"
    !define MIN_FRA_RELEASE "461808"
  !else
    !error "AbortIfBadFramework was passed a bad framework version ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD}."
  !endif
  !else
  !error "AbortIfBadFramework is not sure how to check .NET framework versions > V4.7."
  !endif
  ReadRegDWORD $R9 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" Release
  IntCmp $R9 ${MIN_FRA_RELEASE} end wrong_framework end


   fail:
   fail:
Line 150: Line 209:
   end:
   end:


   ;Pop the variables we pushed earlier
   ; Pop the variables we pushed earlier
   Pop $R8
   Pop $R8
   Pop $R7
   Pop $R7

Revision as of 21:33, 25 June 2018

Author: circumpunct (talk, contrib)


Why?

There are plenty of functions here to detect the .NET Framework, but most (all?) predate Framework 3 and make assumptions that will break them if not met. Specifically that all frameworks will have a build number that can be found under Policy, and that Major (or even Minor) build numbers will not exceed one digit. This may not be true in future.

This function should get around that problem. Frameworks higher than the ones specified are accepted.

I think it's pretty robust, but e-mail me if you find any bugs. Or even better, just fix them. This is a wiki.

Below is another function that gives you the latest version of the installed .NET version. It is based on first script, but uses the VersionCompare macro.

The Abort If Bad Function

;; USAGE:
!define MIN_FRA_MAJOR "3"
!define MIN_FRA_MINOR "0"
!define MIN_FRA_BUILD "*"
;
;; NB Use an asterisk to match anything.
; Call AbortIfBadFramework
;; No pops. It just aborts inside the function, or returns if all is well.
;; Change this if you like.
Function AbortIfBadFramework
 
  ; Save the variables in case something else is using them
  Push $0
  Push $1
  Push $2
  Push $3
  Push $4
  Push $R1
  Push $R2
  Push $R3
  Push $R4
  Push $R5
  Push $R6
  Push $R7
  Push $R8
 
  ; Major
  StrCpy $R5 "0"
 
  ; Minor
  StrCpy $R6 "0"
 
  ; Build
  StrCpy $R7 "0"
 
  ; No Framework
  StrCpy $R8 "0.0.0"
 
  StrCpy $0 0
 
  loop:
 
  ; Get each sub key under "SOFTWARE\Microsoft\NET Framework Setup\NDP"
  EnumRegKey $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP" $0
  StrCmp $1 "" done ;jump to end if no more registry keys
  IntOp $0 $0 + 1
  StrCpy $2 $1 1 ;Cut off the first character
  StrCpy $3 $1 "" 1 ;Remainder of string
 
  ; Loop if first character is not a 'v'
  StrCmpS $2 "v" start_parse loop
 
  ; Parse the string
  start_parse:
  StrCpy $R1 ""
  StrCpy $R2 ""
  StrCpy $R3 ""
  StrCpy $R4 $3
 
  StrCpy $4 1
 
  parse:
  StrCmp $3 "" parse_done ; If string is empty, we are finished
  StrCpy $2 $3 1 ; Cut off the first character
  StrCpy $3 $3 "" 1 ; Remainder of string
  StrCmp $2 "." is_dot not_dot ; Move to next part if it's a dot
 
  is_dot:
  IntOp $4 $4 + 1 ; Move to the next section
  goto parse ; Carry on parsing
 
  not_dot:
  IntCmp $4 1 major_ver
  IntCmp $4 2 minor_ver
  IntCmp $4 3 build_ver
  IntCmp $4 4 parse_done
 
  major_ver:
  StrCpy $R1 $R1$2
  goto parse ; Carry on parsing
 
  minor_ver:
  StrCpy $R2 $R2$2
  goto parse ; Carry on parsing
 
  build_ver:
  StrCpy $R3 $R3$2
  goto parse ; Carry on parsing
 
  parse_done:
 
  IntCmp $R1 $R5 this_major_same loop this_major_more
  this_major_more:
  StrCpy $R5 $R1
  StrCpy $R6 $R2
  StrCpy $R7 $R3
  StrCpy $R8 $R4
 
  goto loop
 
  this_major_same:
  IntCmp $R2 $R6 this_minor_same loop this_minor_more
  this_minor_more:
  StrCpy $R6 $R2
  StrCpy $R7 $R3
  StrCpy $R8 $R4
  goto loop
 
  this_minor_same:
  IntCmp R3 $R7 loop loop this_build_more
  this_build_more:
  StrCpy $R7 $R3
  StrCpy $R8 $R4
  goto loop
 
  done:
 
  ; Have we got the framework we need?
  IntCmp $R5 ${MIN_FRA_MAJOR} max_major_same fail end
  max_major_same:
 
  ; Versions > 4.5 have different logic.
  IntCmp ${MIN_FRA_MAJOR} 4 0 before45 end
  IntCmp ${MIN_FRA_MINOR} 5 max_major_greaterthan45 before45 max_major_greaterthan45
 
  before45:
  IntCmp $R6 ${MIN_FRA_MINOR} max_minor_same fail end
 
  max_minor_same:
  IntCmp $R7 ${MIN_FRA_BUILD} end fail end
 
  max_major_greaterthan45:
 
  !if ${MIN_FRA_MINOR} == "5"
 
   !if ${MIN_FRA_BUILD} == "0"
    !define MIN_FRA_RELEASE "378389"
   !else if ${MIN_FRA_BUILD} == "1"
    !define MIN_FRA_RELEASE "378675"
   !else if ${MIN_FRA_BUILD} == "2"
    !define MIN_FRA_RELEASE "379893"
   !else
    !error "AbortIfBadFramework was passed a bad framework version ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD}."
   !endif
 
  !else if ${MIN_FRA_MINOR} == "6"
 
   !if ${MIN_FRA_BUILD} == "0"
    !define MIN_FRA_RELEASE "393295"
   !else if ${MIN_FRA_BUILD} == "1"
    !define MIN_FRA_RELEASE "394254"
   !else if ${MIN_FRA_BUILD} == "2"
    !define MIN_FRA_RELEASE "394802"
   !else
    !error "AbortIfBadFramework was passed a bad framework version ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD}."
   !endif
 
  !else if ${MIN_FRA_MINOR} == "7"
 
   !if ${MIN_FRA_BUILD} == "0"
    !define MIN_FRA_RELEASE "460798"
   !else if ${MIN_FRA_BUILD} == "1"
    !define MIN_FRA_RELEASE "461308"
   !else if ${MIN_FRA_BUILD} == "2"
    !define MIN_FRA_RELEASE "461808"
   !else
    !error "AbortIfBadFramework was passed a bad framework version ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD}."
   !endif
  !else
   !error "AbortIfBadFramework is not sure how to check .NET framework versions > V4.7."
  !endif
 
  ReadRegDWORD $R9 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" Release
  IntCmp $R9 ${MIN_FRA_RELEASE} end wrong_framework end
 
  fail:
  StrCmp $R8 "0.0.0" no_framework
  goto wrong_framework
 
  no_framework:
  MessageBox MB_OK|MB_ICONSTOP "Installation failed.$\n$\n\
         This software requires Windows Framework version \
         ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD} or higher.$\n$\n\
         No version of Windows Framework is installed.$\n$\n\
         Please update your computer at http://windowsupdate.microsoft.com/."
  abort
 
  wrong_framework:
  MessageBox MB_OK|MB_ICONSTOP "Installation failed!$\n$\n\
         This software requires Windows Framework version \
         ${MIN_FRA_MAJOR}.${MIN_FRA_MINOR}.${MIN_FRA_BUILD} or higher.$\n$\n\
         The highest version on this computer is $R8.$\n$\n\
         Please update your computer at http://windowsupdate.microsoft.com/."
  abort
 
  end:
 
  ; Pop the variables we pushed earlier
  Pop $R8
  Pop $R7
  Pop $R6
  Pop $R5
  Pop $R4
  Pop $R3
  Pop $R2
  Pop $R1
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Pop $0
 
FunctionEnd

The Get-Latest-DotNET Function (warning: register backup/restore code is backwards and generally messed up)

!include "WordFunc.nsh"
!insertmacro VersionCompare
 
Function GetLatestDotNETVersion
 
	;Save the variables in case something else is using them
 
	Push $0		; Registry key enumerator index
	Push $1		; Registry value
	Push $2		; Temp var
	Push $R0	; Max version number
	Push $R1	; Looping version number
 
	StrCpy $R0 "0.0.0"
	StrCpy $0 0
 
	loop:
 
		; Get each sub key under "SOFTWARE\Microsoft\NET Framework Setup\NDP"
		EnumRegKey $1 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP" $0
 
		StrCmp $1 "" done 	; jump to end if no more registry keys
 
		IntOp $0 $0 + 1 	; Increase registry key index
		StrCpy $R1 $1 "" 1 	; Looping version number, cut of leading 'v'
 
		${VersionCompare} $R1 $R0 $2
		; $2=0  Versions are equal, ignore
		; $2=1  Looping version $R1 is newer
        ; $2=2  Looping version $R1 is older, ignore
 
		IntCmp $2 1 newer_version loop loop
 
		newer_version:
		StrCpy $R0 $R1
		goto loop
 
	done:
 
	; If the latest version is 0.0.0, there is no .NET installed ?!
	${VersionCompare} $R0 "0.0.0" $2
	IntCmp $2 0 no_dotnet clean clean
 
	no_dotnet:
	StrCpy $R0 ""
 
	clean:
	; Pop the variables we pushed earlier
	Pop $0
	Pop $1
	Pop $2
	Pop $R1
 
	; $R0 contains the latest .NET version or empty string if no .NET is available
FunctionEnd