2019-03-11更新:原來NSIS腳本也可以禁用64位文件操作重定向的!
1、在安裝腳本的開始處定義 LIBRARY_X64。
!include "MUI.nsh"
!include "Library.nsh"
;如果做32位安裝包就把下句注釋。
!define LIBRARY_X64
2、在調用涉及目標機器上系統目錄(即$SYSDIR)的函數前用 ${DisableX64FSRedirection}。
在安裝包的第一個Section中調用一次即可。
!ifdef LIBRARY_X64
${DisableX64FSRedirection}
!endif
之前問題主要在於64位重定向問題,所以自己用python寫了個腳本。找到了NSIS禁用重定向方法就可以無論32位還是64位都可以使用NSIS來寫腳本了。
原文:
前些天自己做了一年多的軟件成功交付客戶,客戶提出些完善意見,其中一條就是要一個軟件安裝腳本。
這個之前也嘗試python做過,只不過當時有更緊急的任務,最后就沒深入嘗試。
這次我就撿起了之前的python工程,繼續做做。
整個過程很簡單:
1,把軟件解壓到客戶選擇的目錄
2,把一個dll程序復制到windows\system32目錄
3,創建一個桌面快捷方式
因為就這么幾步,所以我以為很容易搞,就選擇了久違的python自己寫,而沒有選擇一些成熟的自動生成腳本工具。
首先肯定要有個界面吧,主要是要用戶選擇安裝目錄。我用Tkinter寫了個簡陋的界面,這個不多說。
解壓壓縮包的話,python有很好的庫zipfile:
def unzip(zipFilePath, destDir): zfile = zipfile.ZipFile(zipFilePath) for name in zfile.namelist(): (dirName, fileName) = os.path.split(name) if fileName == '': # directory newDir = destDir + '/' + dirName if not os.path.exists(newDir): os.mkdir(newDir) else: # file fd = open(destDir + '/' + name, 'wb') fd.write(zfile.read(name)) fd.close() zfile.close()
創建桌面快捷方式python肯定也有庫,但我最后選擇了使用bat腳本。
set Program=這里要寫快捷方式對應的程序目錄,且必須是絕對路徑。
在python里將這個路徑填寫上,然后程序里運行bat腳本即可。
@ echo off set Program= set LnkName=manager software set WorkDir= set Desc=soft if not defined WorkDir call:GetWorkDir "%Program%" (echo Set WshShell=CreateObject("WScript.Shell"^) echo strDesKtop=WshShell.SpecialFolders("DesKtop"^) echo Set oShellLink=WshShell.CreateShortcut(strDesKtop^&"\%LnkName%.lnk"^) echo oShellLink.TargetPath="%Program%" echo oShellLink.WorkingDirectory="%WorkDir%" echo oShellLink.WindowStyle=1 echo oShellLink.Description="%Desc%" echo oShellLink.Save)>makelnk.vbs echo SUCCESS makelnk.vbs del /f /q makelnk.vbs exit goto :eof :GetWorkDir set WorkDir=%~dp1 set WorkDir=%WorkDir:~,-1% goto :eof
上面都算順利,最后竟然在本以為很簡單的復制文件到系統目錄上出了問題。
不管怎樣努力,都沒法將文件復制到windows\system32目錄下。
一開始本以為是權限問題。
在程序開始前加入這樣的代碼:
def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False if is_admin(): #主程序代碼 else: # Re-run the program with admin rights ctypes.windll.shell32.ShellExecuteW(None, u"runas", unicode(sys.executable), unicode(__file__), None, 1)
這樣在運行前就會彈窗要求獲取管理員權限。
按道理這樣程序就已經有了管理員權限了,可還是沒有復制到system32目錄下。
后來在同事幫我看這個問題,他弄了一會,發現其實是64位系統下,系統自動重定向到C:\Windows\SysWOW64目錄下了!
所以一定要在復制操作前,禁止重定向。
with disable_file_system_redirection(): shutil.copy2('sdfp_lib.dll',os.getenv("SystemDrive")+'\\windows\\system32')
上述,便是用python寫我的軟件自動安裝腳本的全過程,后面會附上我的全部代碼。
我先再講下要實現這種軟件自動安裝腳本需求 最常用最合適的實現方法。
其實用工具自動生成就好了!
這個HM NIS Edit工具。
點擊文件,選擇新建腳本向導。
然后按照向導一般的安裝,基本的安裝需求都可以簡單實現。
重點是這一步:
左邊可以添加分組,右邊可以給每個分組添加安裝指令,可以給組添加單獨的文件,也可以給組添加主程序目錄。每個組再配置安裝目標目錄。這個目標目錄有很多選擇,包括系統目錄、用戶選擇目錄…………不贅述。
這個工具編譯好腳本,就生成了一個Setup.exe文件。這就是安裝程序。要安裝的軟件文件都包含在這個exe里了,很厲害。
按道理,只要用這個工具就可以完成我的需求了,但在64位系統還有些問題,那就是依然會有系統重定向現象。本來要復制到system32目錄下的dll還是會被復制到C:\Windows\SysWOW64下。
最后我就決定,做兩個版本。
32位的安裝程序用HM NIS Edit工具自動生成。
64位我自己用python寫。
另外,python轉化成exe文件的寫法,之前文章介紹過:
https://www.cnblogs.com/rixiang/p/7274026.html
附上py完整代碼:
# -*- coding: utf-8 -*- from __future__ import print_function from Tkinter import * import os import sys import subprocess import shutil reload(sys) defaultencoding = 'utf-8' import ctypes import tkFileDialog as filedialog import zipfile from shutil import copyfile class disable_file_system_redirection: _disable = ctypes.windll.kernel32.Wow64DisableWow64FsRedirection _revert = ctypes.windll.kernel32.Wow64RevertWow64FsRedirection def __enter__(self): self.old_value = ctypes.c_long() self.success = self._disable(ctypes.byref(self.old_value)) def __exit__(self, type, value, traceback): if self.success: self._revert(self.old_value) def unzip(zipFilePath, destDir): zfile = zipfile.ZipFile(zipFilePath) for name in zfile.namelist(): (dirName, fileName) = os.path.split(name) if fileName == '': # directory newDir = destDir + '/' + dirName if not os.path.exists(newDir): os.mkdir(newDir) else: # file fd = open(destDir + '/' + name, 'wb') fd.write(zfile.read(name)) fd.close() zfile.close() def choose_directory(): global dir_choosen global dir_choosen2 dir_choosen = filedialog.askdirectory(initialdir='C:') # unzip my program to directory choosen dir_choosen2 = dir_choosen dir_choosen = dir_choosen + '/tgsoft' if not os.path.exists(dir_choosen): os.makedirs(dir_choosen) entryText.set(dir_choosen) def install(): if dir_choosen2.strip()=='' or dir_choosen.strip()=='': return -1 unzip('tgsoft.zip',dir_choosen) with disable_file_system_redirection(): shutil.copy2('sdfp_lib.dll',os.getenv("SystemDrive")+'\\windows\\system32') str_bat = '' f = open('CREATE_SHORTCUT.bat', 'r') line = f.readline() while line: str_bat+=line line = f.readline() f.close() nPos=str_bat.index('=')+1 str_bat = str_bat[:nPos]+dir_choosen2+"\\tgsoft\\ManagerSoftware.exe"+str_bat[nPos:] f = open('CREATE_SHORTCUT2.bat', 'w') # 若是'wb'就表示寫二進制文件 f.write(str_bat) f.close() child = subprocess.Popen('CREATE_SHORTCUT2.bat',shell=False) # reset the window file_label.destroy() file_entry.destroy() file_btn.destroy() b2.destroy() w = Label(master, text="安裝成功\n感謝使用") w.grid(row=0) def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False if is_admin(): global master master = Tk() master.title('指靜脈注冊軟件安裝程序') master.geometry('400x100') global file_label file_label = Label(master, text="選擇軟件安裝路徑") file_label.grid(row=0) global entryText entryText = StringVar() global file_entry file_entry = Entry(master,textvariable=entryText) file_entry.grid(row=0, column=1) global file_btn file_btn = Button(master, text='點擊選擇路徑', command=choose_directory) file_btn.grid(row=0,column=2) global b1 b1 = Button(master, text=' 退 出 ', command=master.quit) b1.grid(row=1,column=0) global b2 b2 = Button(master, text=' 確 定 ', command=install) b2.grid(row=1,column=1) mainloop() else: # Re-run the program with admin rights ctypes.windll.shell32.ShellExecuteW(None, u"runas", unicode(sys.executable), unicode(__file__), None, 1)