SMTP 서버를 통해 메일 보내는 커맨드 라인 프로그램

SMTP 서버를 통해 메일을 보내는 커맨드 라인 기반 프로그램.
NSIS에 내장하면 좋을것 같음.

http://code.google.com/p/mailsend/

아래는 gmail 을 통한 메일 보내는 예제.

mailsend.exe -to %1 -from %2 -sub "test from windows" -starttls -port 587 -auth -smtp smtp.gmail.com -user "%2" -pass %SMTP_USER_PASS% -M "hello one liner" -log "c:\mailsend.log"

실제 테스트 해보니 아주~~ 잘간다.

다운로드: mailsend1.17b14.7z (소스/Win32 바이너리 포함)

NSIS 확장자 연결

우선.. 아래의 헤더파일을 다운로드 받고.

http://nsis.sourceforge.net/FileAssoc

스크립트에서는 아래와 같이 함.

확장자 연결시..

	!insertmacro APP_ASSOCIATE "mtp" "mytcl.projectfile" "MyTcl Project File" "$INSTDIR\mytcl.exe,0" \
		"Open with MyTcl" "$INSTDIR\mytcl.exe $\"%1$\""
	!insertmacro APP_ASSOCIATE "tcl" "mytcl.tclfile" "Tcl File" "$INSTDIR\mytcl.exe,0" \
		"Edit with MyTcl" "$INSTDIR\mytcl.exe $\"%1$\""
	!insertmacro UPDATEFILEASSOC

확장자 제거시..

	!insertmacro APP_UNASSOCIATE "mtp" "mytcl.projectfile"
	!insertmacro APP_UNASSOCIATE "tcl" "mytcl.tclfile"
	!insertmacro UPDATEFILEASSOC

NSIS에서 레지스트리 검색

만약 HKLM 하단에 밑의 이미지와 같이 ApplicationPath 라는 레지스트리 키가 있는지와

그의 값을 얻고자 한다면.. Registry 플로그인을 사용함.

http://nsis.sourceforge.net/Registry_plug-in

Registry.zip

아래와 같이 간단히 구현할 수 있음.

!include Registry.nsh

Function .onInit
	${registry::Open} "HKEY_LOCAL_MACHINE" "/K=0 /V=1 /S=0 /B=1 /N='ApplicationPath'" $0
	StrCmp $0 0 0 loop

	loop:
	; $1: Path, $2: Key, $3: Value
	${registry::Find} "$0" $1 $2 $3 $4

	${If} $3 == ""
		MessageBox MB_ICONINFORMATION|MB_OK \
			"Not found."
		${registry::Close} "$0"
		${registry::Unload}
		Abort
	${EndIf}

	; Default install directory
	StrCpy $INSTDIR $3

	; Memory free
	${registry::Close} "$0"
	${registry::Unload}
FunctionEnd

NSIS 64비트 지원을 위한 팁

출처: http://www.indidev.net/forum/viewtopic.php?t=69

1. UNICODE NSIS 설치 : http://www.scratchpaper.com/

WIN95/98 을 지원하지 않는다고 할때 UNICODE NSIS 를 사용하면 설치파일의 다국어 지원등에

매우 유리합니다. 물론 64비트 OS 는 전부 유니코드 지원 OS 죠.

2. Program files 폴더

nsis 에서 program files 폴더 변수는 $PROGRAMFILES 입니다.

그런데 64비트 os 에서는 $PROGRAMFILES 는 program files (x86) 이 되어 버리죠.

64비트용 프로그램을 program files 에 설치하고 싶으면 $PROGRAMFILES64 사용하면 됩니다.

$PROGRAMFILES64 변수는 32비트 os 에서도 program files 입니다.

3. 레지스트리 접근

64비트 os 는 32비트용 프로그램이 직접적으로 64비트 os 용 레지스트리에 접근하는걸 막습니다.

마찬가지로 nsis 스크립트의 설치파일도 그냥 쓰면 64비트용 레지스트리에 접근이 안되기 때문에

64비트용 레지스트리에 접근하기 위해서는

SetRegView   64

를 써 줘야만 합니다.

4. 런타임에 64비트 os 여부 확인

32비트 바이너리와 64비트 바이너리를 한 설치파일에 집어넣고자 할 경우가 있습니다.

이런 경우 런타임에 현재 os 가 64 비트인지 아닌지 확인하려면 다음과 같은 방식으로 코드를 사용하면 됩니다.

   ; 64비트 여부 체크하기   
   System::Call "kernel32::GetCurrentProcess() i .s"
   System::Call "kernel32::IsWow64Process(i s, *i .r0)"
   StrCmp $0 '0' Win32 Win64
   Win32:
      File /oname=name.exe  name32.exe
      Goto EndCheck
   Win64:
      File /oname=name.exe  name64.exe
   EndCheck:

NSIS 설치 디렉토리에 ‘/ ‘ 문제

NSIS에 기본 설치 디렉토리 InstallDir 변수에 C:/Install/1.0 과 같은 식의 패스를 넣어주면 설치가 되지 않는데 굳이 C:\Install\1.0 대신에 C:/Install/1.0 을 넣고자 한다면 NSIS의 다음의 매크로를 이용한다.

!macro StrSlash un
  Function ${un}StrSlash
	  Exch $R3 ; $R3 = needle ("\" or "/")
	  Exch
	  Exch $R1 ; $R1 = String to replacement in (haystack)
	  Push $R2 ; Replaced haystack
	  Push $R4 ; $R4 = not $R3 ("/" or "\")
	  Push $R6
	  Push $R7 ; Scratch reg
	  StrCpy $R2 ""
	  StrLen $R6 $R1
	  StrCpy $R4 "\"
	  StrCmp $R3 "/" loop
	  StrCpy $R4 "/"  
	loop:
	  StrCpy $R7 $R1 1
	  StrCpy $R1 $R1 $R6 1
	  StrCmp $R7 $R3 found
	  StrCpy $R2 "$R2$R7"
	  StrCmp $R1 "" done loop
	found:
	  StrCpy $R2 "$R2$R4"
	  StrCmp $R1 "" done loop
	done:
	  StrCpy $R3 $R2
	  Pop $R7
	  Pop $R6
	  Pop $R4
	  Pop $R2
	  Pop $R1
	  Exch $R3
  FunctionEnd
!macroend

!insertmacro StrSlash ""
!insertmacro StrSlash "un."

그리고 다음과 같이 이용한다.

Push $INSTDIR; c:\this\and\that\filename.htm
Push "\"
Call StrSlash
Pop $R0
; Now $R0 contains 'c:/this/and/that/filename.htm'

Uninstall 섹션에서는 다음과 같이 호출한다.

..
Call un.StrSlash
..

설치 위치 변경 못하게 막기

아래는 MyTcl 인스톨러에 사용된 코드.

; Directory page
!define MUI_PAGE_CUSTOMFUNCTION_SHOW DirectoryShow
!insertmacro MUI_PAGE_DIRECTORY

Function DirectoryShow
	; 설치 위치 지정 못하도록 막음
	FindWindow $R1 "#32770" "" $HWNDPARENT
	GetDlgItem $R2 $R1 1019 # Dir box
	EnableWindow $R2 0
	GetDlgItem $R2 $R1 1001 # Browse button
	EnableWindow $R2 0
FunctionEnd

installer

레지스트리 키 존재여부 체크

아래는 MyTcl 인스톨러에 사용된 코드.

	ClearErrors
	EnumRegKey $1 "${PRODUCT_UNINST_ROOT_KEY}" "${PRODUCT_UNINST_KEY}\Package" ""
	IfErrors 0 key_exist
		Goto key_no_exist
key_exist:
		MessageBox MB_ICONINFORMATION|MB_OK \
			"Package is installed in your system. please uninstall package(s) first."
		Abort
key_no_exist:
		; 설치 되어 있지 않으므로 진행

DLL 버전비교 플러그인

아래의 코드는 DLL 버전을 비교하는 플러그인이다. 회사제품 인스톨러 제작시 필요하여 직접 제작해 보았다. 이 플러그인은 이미 설치되어 있는 버전을 비교하여 때에 따라 설치할때(최신 버전일때..) 사용하면 유용하다.

DllUtil.c

/*
 *
 * Programming by inhak.min@gmail.com
 * Dynalith Systems 2009
 *
 * NSIS plugin - DLL related commands
 *
 */

#include <windows.h>
#include <shlwapi.h>
#include "extdll.h"

#include <sys/stat.h> 

BOOL FileExists(char* strFilename) { 
  struct stat stFileInfo; 
  BOOL blnReturn; 
  int intStat; 

  intStat = stat(strFilename,&stFileInfo); 
  if(intStat == 0) { 
    blnReturn = TRUE; 
  } else { 
    blnReturn = FALSE; 
  } 
   
  return(blnReturn); 
}

BOOL GetFileVersion(LPCTSTR lpszFilePath, VS_FIXEDFILEINFO *pvffi) 
{ 
   BOOL                bRet = FALSE; 
   DWORD               dwSize; 
   BYTE                *pByte; 
   VS_FIXEDFILEINFO    *pvffi0; 
   UINT                nLen = sizeof *pvffi0;
   dwSize = GetFileVersionInfoSize(lpszFilePath, NULL);
   if(dwSize > 0) 
   { 
       pByte = malloc(dwSize);
       if(GetFileVersionInfo(lpszFilePath, NULL, dwSize, pByte) == TRUE &&
          VerQueryValue(pByte, "\\", (LPVOID*) &pvffi0, &nLen) == TRUE) 
       { 
           memcpy(pvffi, pvffi0, sizeof *pvffi); 
           bRet = TRUE; 
       }
       free(pByte); 
   }
   return bRet; 
}

int CompareVersion(VS_FIXEDFILEINFO *f0, VS_FIXEDFILEINFO *f1) 
{ 
   int nRet = 0; 
   int diffMS, diffLS;
   diffMS = f1->dwFileVersionMS - f0->dwFileVersionMS; 
   diffLS = f1->dwFileVersionLS - f0->dwFileVersionLS;
   if(diffMS == 0) 
       nRet = diffLS; 
   else 
       nRet = diffMS;
   return nRet; 
}

// DLL version check
void __declspec(dllexport) CompVer(HWND hwndParent, int string_size, char *variables, stack_t **stacktop)
{
	VS_FIXEDFILEINFO f0; 
	VS_FIXEDFILEINFO f1; 

	char file1[256], file2[256];

        EXDLL_INIT();

	popstring(file1);
	popstring(file2);

	// 비교할 파일이 없으므로 1로 셋팅 (무조건 설치)
	if ( FileExists(file1) == FALSE ) goto error;

	if ( GetFileVersion(file1, &f0) == FALSE ) goto error;
	if ( GetFileVersion(file2, &f1) == FALSE ) goto error;

	if(CompareVersion(&f0, &f1) > 0) 
	{
		// new
		setuservariable(INST_R0, "1");
		return;
	}
	else
	{
		// older or same
		setuservariable(INST_R0, "0");
		return;
	}
		
error:
	setuservariable(INST_R0, "1");
	return;
} 

make.bat

@call vcvars32.bat
@cl -c DllUtil.c
if errorlevel 1 goto error
@cl /LD /o DllUtil.dll DllUtil.obj version.lib user32.lib
:error
@pause

update.bat

@copy DllUtil.dll "C:\Program Files\NSIS\Plugins"
@pause

test.nsi

;--------------------------------

; The name of the installer
Name "CompDllVer"

; The file to write
OutFile "test.exe"

; The default installation directory
InstallDir $PROGRAMFILES\DllUtil

;--------------------------------

; Pages

Page components
Page directory
Page instfiles

UninstPage uninstConfirm
UninstPage instfiles

;--------------------------------

; The stuff to install
Section "CompDllVer (required)"
  ; Set output path to the installation directory.
  SetOutPath $INSTDIR
  
  DllUtil::CompVer "$SYSDIR\test.dll" "test.dll"
  Pop $R0

  ; 설치되어 있는 파일보다 최신이면 설치
  StrCmp $R0 '0' skip
          SetOutPath $SYSDIR
	  File "test.dll"
	  goto +3
  skip:
  MessageBox MB_OK "skipped"

  SetOutPath $INSTDIR
  WriteUninstaller "uninstall.exe"
  
SectionEnd

;--------------------------------

; Uninstaller

Section "Uninstall"
  ; Remove files and uninstaller
  Delete $SYSDIR\\test.dll
  Delete $INSTDIR\uninstall.exe

  ; Remove directories used
  RMDir "$SMPROGRAMS\DllUtil"
  RMDir "$INSTDIR"
SectionEnd

플러그인 개발시 변수처리 방법

NSIS 플러그인 개발시 변수처리 방법에 대해 적어둔다.

변수 읽기

NSIS 변수 $R0에서 문자열을 읽음

printf("$R0=%s\n",getuservariable(INST_R0));

변수 쓰기

NSIS 변수 $R0에 test문자열을 기록

setuservariable(INST_R0, "test");

인자 읽기

안자는 연속된 값으로 popstring으로 읽는다.

char arg1[256];
char arg2[256];
popstring(arg1);
popstring(arg2);