C#/기술 개발

프로그램 배포, 인스톨 파일 만들기 프로젝트 (feat NSIS)

sheepone 2021. 6. 7. 11:15
반응형

 

프로젝트 목적
프로그램 배포시에 인스톨 파일을 이용하여 편리한 프로그램 배포와 버전별 자동 백업 기능

 

ONECLICK

 

무료이나 배포위치를 변경할수 없음에 현재 장비에 사용하기에는 어려움
(인터넷 배포등에는 가능)

 

MICROSOFT INSTALLER

 

express 버전에서는 확장 프로그램 설치가 제한되어 있어 사용이 불가(사용중인 버전 , 무료)
community 버전에선는 사용이 가능하나 매출 10억이하인 기업에서 5개까지 프리라인선스
정식버전 구매시 사용가능하나 약 60만원 (Microsoft installer는 무료)

 

Install shield

 

InstallShield Professional 가격: ₩ 3,144,300
1 시스템당 1 소프드웨어 라이센스가 필요함

InstallShield Express 가격: ₩ 812,400
소프트웨어 라이센스는 PC와 사용자마다 1개가 필요합니다.

InstallShield Limited Edition
2013에서는 무료이나(express 제외)
2015에서는 Visual Studio Professional 및 Enterprise Edition 사용자에 대해 무료로 제공
2017에서는 제외됨

 

NSIS 

 

무료 배포 프로그램 스트립트 기반
7z을 이용하여 압축한 파일과 설치 파일로 구성.
스크립트로 작성필요 , 필요파일 압축 필요 , 설치파일 만들기 필요

- Advanced Installer for Visual Studio 2015

프로젝트에 직접 추가하는 방식이 아닌것들은 모두 제외
(컴파일후 다시 작업해야 하는 이중작업)
=> MICROSOFT INSTALLER , Install shield 는 라이선스 문제로 사용이 어려움에 NSIS 채택

 

Nsis 스크립트를 프로젝트별 자동 생성하여 인스톨 파일만들기
(고객사에 업데이트 버전을 송부 할때 쓰기 위함)

 

  1. 인스톨 파일 생성을 위한 스크립트 생성하기
    프로젝트가 변경될때 다시 입력해 주어야 하는 부분을 자동으로 입력하게 하여 자동화
    기존 파일 백업하기 
    (BACKUP\VERSION\폴더내에 기존 파일들을 전부 백업 , 
    기존 파일의 VERSION을 받아와 버전이름별로 백업수행)
    설치위치를 입력 가능하게 하여 위치별 대응

  2. 라이선스 파일 만들기
    소프트웨어 최종 사용자 사용권 문서 작성 (알약 문서를 토대로 작성)
    소프트웨어 최종 사용자 사용권 영문판 작성 (기본 영문판으로 배포하려고 하기 위함)

  3. 빌드후 자동으로 인스톨 파일 생성하기
    빌드후 이벤트를 이용하여 빌드후에 자신을 실행시켜 스크립트 파일을 생성후
    NSIS를 이용 인스톨 파일 생성후 나머지 파일은 삭제

  4. 테스트
    일반 PC에서 설치시 문제 없음 확인

  5. 추가 업데이트 부분
    라이선스 언어팩 분리 (한글 , 영어)
    언어별 에러 리스트 추가

 

메인 프로그램

 

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

//------------------------------------------------------------------------------
//NSIS를 이용한 설치파일 만들기 (김동우)
//NUGET 에서 NSIS 설치 필요
//실행시에 설치 폴더를 BACKUP\VERSION\에 백업하고 수행
//빌드 이벤트에 실행후 파일삭제(매번 수행되게 하는 방법으로 사용)

//파일 추가하기
//필요한 파일은 info.File.Add를 이용하여 추가
//------------------------------------------------------------------------------

namespace Installer
{
    public class Eqp
    {
        public const string sEqpName  = "Machine";
        //------------------------------------------------------------------------------
        //언어관련
        public const string sKorean   = "Korean"    ; //한국어
        public const string sEnglish  = "English"   ; //영어
        public const string sChinese  = "Chinese"   ; //중국어 간체
        public static string sLanguage = sEnglish   ;
    }

    class Program
    {
        #region DllImport
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

        [DllImport("user32.dll")]
        private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("user32.dll", EntryPoint = "FindWindow")]
        private static extern IntPtr FindWindow(string IPClassName, String IpWindowName);

        [DllImport("User32.dll", EntryPoint = "SetForegroundWindow")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        [DllImport("kernel32.dll")]
        static extern IntPtr GetConsoleWindow();
        
        //[DllImport("user32.dll")]
        //static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        const int SW_HIDE = 0; // 숨기기
        const int SW_SHOW = 1; // 보이기
        #endregion

        #region Variable
        static string sError ;
        static List<string> listScript = new List<string>();
        static string stext;
        static public  string sText
        {
            get { return stext; }
            set {
                stext = value;
                listScript.Add(stext.Trim());
                //if(stext != Environment.NewLine) richTextBox1.AppendText(stext + "\n");
                //else                             richTextBox1.AppendText(stext);
            }
        }

        struct Info
        {
            public string PRODUCT_NAME       ;  
            public string PRODUCT_VERSION    ;
            public string PRODUCT_PUBLISHER  ;
            public string PRODUCT_WEB_SITE   ;
            public string PRODUCT_DIR_REGKEY ;
                           
            public string MUI_ICON           ;
            public string MUI_PAGE_LICENSE   ;
            public string MUI_FINISHPAGE_RUN ;
            public string MUI_LANGUAGE       ;
                           
            public string OutFile            ;
            public string InstallDir         ;
            public string InstallDirRegKey   ;
                           
            public List<string> SetOutPath   ;
            public List<string> File         ;

        };
        static Info info ;

        #endregion

        static void Main(string[] args)
        {
            //var handle = GetConsoleWindow();
            //ShowWindow(handle, SW_HIDE); // 숨기기

            sError = "" ;

            info.SetOutPath = new List<string>();
            info.File       = new List<string>();

            SetInfo  (); //정보 입력 하기
            SetScript(); //스크립트 만들기
            Make     (); //인스톨 파일 만들기

            if(sError != "") {
                //Console.WriteLine("INSTALL ERROR \r\nFile not found : " + sError);
                MessageBox.Show("FILE NOT FOUND : " + sError , "INSTALL ERROR");
                //Console.ReadKey();
                //MessageBox.Show("File not found : " + sError , "Install Error");
            }
        }

        #region Form
        /*
        public static Install Instance { 
			get { return instance == null ? (instance = new Install()) : instance; } 
		}
		private static Install instance = null;

        public Install()
        {
            //var handle = GetConsoleWindow();
            //ShowWindow(handle, SW_HIDE); // 숨기기

            sError = "" ;

            info.SetOutPath = new List<string>();
            info.File       = new List<string>();

            SetInfo  ();
            SetScript();
            Make     ();

            if(sError != "") {
                MessageBox.Show("File not found : " + sError , "Install Error");
            }
        }
        */
        #endregion

        static private void SetInfo()
        {
            var directory = Environment.CurrentDirectory         ;
            
            DirectoryInfo directoryInfo = Directory.GetParent(directory).Parent;
            directory = directoryInfo.FullName ;

            info.File.Add(@"\Bin\Machine.exe"                  );


            
            var PathLicense     = directory + @"\Installer\Resources\License.txt"    ;

            switch(Eqp.sLanguage)
            {
                default       : 
                    info.File.Add(@"\Bin\Util\Error_E.ini"   );
                    PathLicense = directory + @"\Installer\Resources\License_E.txt"    ;
                    break;
                case Eqp.sEnglish: 
                    info.File.Add(@"\Bin\Util\Error_E.ini"   );
                    PathLicense = directory + @"\Installer\Resources\License_E.txt"    ;
                    break;
                case Eqp.sKorean: 
                    info.File.Add(@"\Bin\Util\Error_K.ini"   );
                    PathLicense = directory + @"\Installer\Resources\License_K.txt"    ;
                    break;
                case Eqp.sChinese: 
                    info.File.Add(@"\Bin\Util\Error_C.ini"   );
                    PathLicense = directory + @"\Installer\Resources\License_E.txt"    ;
                    break;
            }

            FileInfo file;
            for(int i=0; i<info.File.Count; i++)
            {
                info.SetOutPath.Add(@"$INSTDIR" + Path.GetDirectoryName(info.File[i]) + @"");
                info.File[i] = directory + info.File[i];
                file = new FileInfo(info.File[i]);
                if(!file.Exists) sError = info.File[i] ;
            }

            //첫번째걸로 버전 체크
            var PathExe = info.File[0] ; 
            file = new FileInfo(PathExe);
            PathExe = file.FullName;

            //아이콘 라이센스 파일
            var PathIcon        = directory + @"\Installer\Resources\Circle.ico";
            //var PathLicense     = directory + @"\Installer\Resources\License.txt"    ;
            file = new FileInfo(PathIcon   ); if(!file.Exists) sError =  PathIcon    ;
            file = new FileInfo(PathLicense); if(!file.Exists) sError =  PathLicense ;
            
            info.PRODUCT_NAME       = @"""" + Eqp.sEqpName + @"""";
            info.PRODUCT_VERSION    = @"""" + GetVer(PathExe)      + @"""";
            info.PRODUCT_PUBLISHER  = @"""동우의하루 PRECISION ENG.CO.,LTD""";
            info.PRODUCT_WEB_SITE   = @"""http://www.동우의하루.co.kr""";
            info.PRODUCT_DIR_REGKEY = @"""Software\Microsoft\Windows\CurrentVersion\App Paths\Control.exe""";
                        
            info.MUI_ICON           = @"""" + PathIcon    + @"""";
            info.MUI_PAGE_LICENSE   = @"""" + PathLicense + @"""";
            info.MUI_FINISHPAGE_RUN = @"""" + PathExe     + @"""";
            //info.MUI_LANGUAGE       = @"""Korean""";
            //info.MUI_LANGUAGE       = @"""English""";
            info.MUI_LANGUAGE       = @"""" + Eqp.sLanguage + @"""";
            

            info.OutFile            = @"""Install.exe""";            
            //info.OutFile            = @"""" + Machine.Eqp.sEqpName + @".exe""";
            info.InstallDir         = @"""" + directory + @"""";
            info.InstallDirRegKey   = @"HKLM ""${PRODUCT_DIR_REGKEY}"" """"";

        }

        static private void SetScript()
        {
            sText = "; Script generated by the HM NIS Edit Script Wizard."; 
            sText = Environment.NewLine;
            sText = @"; HM NIS Edit Wizard helper defines";
            sText = @"!define PRODUCT_NAME "       + info.PRODUCT_NAME       ;
            sText = @"!define PRODUCT_VERSION "    + info.PRODUCT_VERSION    ;
            sText = @"!define PRODUCT_PUBLISHER "  + info.PRODUCT_PUBLISHER  ;
            sText = @"!define PRODUCT_WEB_SITE "   + info.PRODUCT_WEB_SITE   ;
            sText = @"!define PRODUCT_DIR_REGKEY " + info.PRODUCT_DIR_REGKEY ;
            sText = Environment.NewLine;
            sText = @"; MUI 1.67 compatible ------";
            sText = @"!include ""MUI.nsh""";
            sText = Environment.NewLine;
            sText = @"; MUI Settings";
            sText = @"!define MUI_ABORTWARNING";
            sText = @"!define MUI_ICON "              + info.MUI_ICON ;
            sText = Environment.NewLine;
            sText = @"; Welcome page";
            sText = @"!insertmacro MUI_PAGE_WELCOME";
            sText = @"; License page";
            sText = @"!insertmacro MUI_PAGE_LICENSE " + info.MUI_PAGE_LICENSE;
            sText = @"; Directory page";
            sText = @"!insertmacro MUI_PAGE_DIRECTORY";
            sText = @"; Instfiles page";
            sText = @"!insertmacro MUI_PAGE_INSTFILES";
            sText = @"; Finish page";
            sText = @";!define MUI_FINISHPAGE_RUN " + info.MUI_FINISHPAGE_RUN;
            sText = @"!insertmacro MUI_PAGE_FINISH";
            sText = Environment.NewLine;
            sText = @"; Language files";
            sText = @"!insertmacro MUI_LANGUAGE "   + info.MUI_LANGUAGE;
            sText = Environment.NewLine;
            sText = @"; MUI end ------";
            sText = Environment.NewLine;
            sText = @"Name ""${PRODUCT_NAME} ${PRODUCT_VERSION}""";
            sText = @"OutFile "          + info.OutFile         ;
            sText = @"InstallDir "       + info.InstallDir      ;
            sText = @"InstallDirRegKey " + info.InstallDirRegKey;
            sText = @"ShowInstDetails show";
            sText = Environment.NewLine;

            //Function Stt
            sText = @"Function GetFileVersion";
            sText = @"	!define GetFileVersion `!insertmacro GetFileVersionCall`";

            sText = @"	!macro GetFileVersionCall _FILE _RESULT";
            sText = @"		Push `${_FILE}`";
            sText = @"		Call GetFileVersion";
            sText = @"		Pop ${_RESULT}";
            sText = @"	!macroend";

            sText = @"	Exch $0";
            sText = @"	Push $1";
            sText = @"	Push $2";
            sText = @"	Push $3";
            sText = @"	Push $4";
            sText = @"	Push $5";
            sText = @"	Push $6";
            sText = @"	ClearErrors";

            sText = @"	GetDllVersion '$0' $1 $2";
            sText = @"	IfErrors error";
            sText = @"	IntOp $3 $1 >> 16";
            sText = @"	IntOp $3 $3 & 0x0000FFFF";
            sText = @"	IntOp $4 $1 & 0x0000FFFF";
            sText = @"	IntOp $5 $2 >> 16";
            sText = @"	IntOp $5 $5 & 0x0000FFFF";
            sText = @"	IntOp $6 $2 & 0x0000FFFF";
            sText = @"	StrCpy $0 '$3.$4.$5.$6'";
            sText = @"	goto end";

            sText = @"	error:";
            sText = @"	SetErrors";
            sText = @"	StrCpy $0 ''";

            sText = @"	end:";
            sText = @"	Pop $6";
            sText = @"	Pop $5";
            sText = @"	Pop $4";
            sText = @"	Pop $3";
            sText = @"	Pop $2";
            sText = @"	Pop $1";
            sText = @"	Exch $0";
            sText = @"FunctionEnd";
            sText = Environment.NewLine;
            //Function End

            sText = @"Section ""MainSection"" SEC01";
            //sText = @";  CreateDirectory "$SMPROGRAMS\테스트 #1"
            //sText = @";  CreateShortCut "$SMPROGRAMS\테스트 #1\테스트 #1.lnk" "$INSTDIR\Control.exe"
            //sText = @";  CreateShortCut "$DESKTOP\테스트 #1.lnk" "$INSTDIR\Control.exe"
            sText = @"  SetOverwrite on";

            string sFileName = @"""" + info.SetOutPath[0] + @"\" + Path.GetFileName(info.File[0]) + @"""" ;
            sText = @"Var /GLOBAL Version";
            sText = @"${GetFileVersion} " + sFileName + " $version";
            sText = @"CopyFiles ""$INSTDIR\Bin\*.*"" ""$INSTDIR\BACKUP\$Version\""";
            //sText = @"SetOutPath ""$INSTDIR\BACKUP\$version\""";
            //sText = @"File /nonfatal /a /r ""$INSTDIR\Bin\"" #note back slash at the end";
            for(int i=0; i<info.File.Count; i++)
            {
                sText = @"  SetOutPath """ + info.SetOutPath[i]  + @"""";
                sText = @"  File """       + info.File[i]        + @""""; 
            }
            sText = @"SectionEnd";

            
        }

        static public void Make()
        {
            //richTextBox1.SaveFile(@"D:\Temp\qqq.nsi");
            var directory = Environment.CurrentDirectory         ;
            DirectoryInfo directoryInfo = Directory.GetParent(directory).Parent;
            directory = directoryInfo.FullName ;

            var InstallFile = directory + @"\Install.nsi" ;
            var NsisFile    = directory + @"\packages\NSIS.2.51\tools\makensis.exe";

            FileInfo file = new FileInfo(NsisFile); if(!file.Exists) sError =  NsisFile ;

            StreamWriter sw = new StreamWriter(InstallFile);
            for(int i=0; i<listScript.Count; i++)
            {
                sw.WriteLine(listScript[i]);
            }
            sw.Close();


            System.Diagnostics.ProcessStartInfo proInfo = new System.Diagnostics.ProcessStartInfo();
            System.Diagnostics.Process pro = new System.Diagnostics.Process();

            // 실행할 파일명 입력 -- cmd
            proInfo.FileName = @"cmd";
            // cmd 창 띄우기 -- true(띄우지 않기.) false(띄우기)
            proInfo.CreateNoWindow = false;
            proInfo.UseShellExecute = false;
            // cmd 데이터 받기
            proInfo.RedirectStandardOutput = true;
            // cmd 데이터 보내기
            proInfo.RedirectStandardInput = true;
            // cmd 오류내용 받기
            proInfo.RedirectStandardError = true;
           
            pro.StartInfo = proInfo;           
            pro.Start();
               
            // CMD 에 보낼 명령어를 입력 합니다.
            pro.StandardInput.Write(NsisFile + " " + InstallFile + Environment.NewLine);
            pro.StandardInput.Close();

            // 결과 값을 리턴 받습니다.
            string resultValue = pro.StandardOutput.ReadToEnd();
            pro.WaitForExit();
            pro.Close();
        }

        static private string GetVer(string _sName)
        {
            //Version vVer ;
            string sVer ;
            string sName = _sName;
            try { 
                //vVer = System.Reflection.AssemblyName.GetAssemblyName(sName).Version ;
                FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(sName);
                sVer = fvi.FileVersion ;
            }
            catch (Exception _e)
            {
                sVer = "Loading Failed - " + _e.Message ;
            }
            return sVer;
        }

    }
}

 

빌드 이벤트 추가

 

생성되어 있던 인스톨러를 삭제 하고 새로 만듬

Del $(ProjectDir)\..\Installer.exe 
Del $(ProjectDir)\..\Install.nsi
$(ProjectDir)$(OutDir)installer.exe
Del $(ProjectDir)$(OutDir)install.exe
Del $(ProjectDir)\..\Install.nsi

 

빌드 - 구성관리자 셋팅

 

 

Debug 일시에는 Installer 프로젝트를 빌드 안함
(인스톨 파일 만드는데 아무래도 시간이 좀 걸려서 디버깅시까지 하기에는 로스가 발생)
Release 일시에만 Installer 프로젝트를 빌드
(버전별로 백업하기에 빌드전에 버전 변경 필요)

 

 

관련 프로젝트 첨부

 

Installer.zip
0.03MB

반응형