2019-03-11更新:原来NSIS脚本也可以禁用64位文件操作重定向的!
1、在安装脚本的开始处定义 LIBRARY_X64。
!include "MUI.nsh"
!include "Library.nsh";如果做32位安装包就把下句注释。
!define LIBRARY_X642、在调用涉及目标机器上系统目录(即$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 offset 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=1echo oShellLink.Description="%Desc%"echo oShellLink.Save)>makelnk.vbsecho SUCCESSmakelnk.vbsdel /f /q makelnk.vbsexitgoto :eof:GetWorkDirset WorkDir=%~dp1set WorkDir=%WorkDir:~,-1%goto :eof
上面都算顺利,最后竟然在本以为很简单的复制文件到系统目录上出了问题。
不管怎样努力,都没法将文件复制到windows\system32目录下。
一开始本以为是权限问题。
在程序开始前加入这样的代码:
def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return Falseif 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_functionfrom Tkinter import *import osimport sysimport subprocessimport shutilreload(sys)defaultencoding = 'utf-8'import ctypesimport tkFileDialog as filedialogimport zipfilefrom shutil import copyfileclass 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 Falseif 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)