JoyLau's Blog

JoyLau 的技术学习与思考

什么是套娃图标

可能见过一种套娃的玩具,就是一个大的套着一个小的,每一个玩具的形状颜色都一样,只是大小比例不一样,套娃图标也是这个意思

什么工具

需要这么一个工具, IconFX : 下载

IconFX

制做一组至少有 256256 (此外还有 128128 , 9696, 6464, 4848,3232,16*16)的一套图标, Windows 下格式为 icon, Mac 下格式为 icns

怎么制作

使用 PS 制作一张图片或者下载一张图片,按照下面的步骤完成所有大小的图标创建,之后保存.

图像 — 从图像创建 Windows 图标

IconFX-make

MacOS 也是同样的道理

什么是 URL Scheme 协议

个人理解为注册一种协议来实现应用间的跳转

Windows 上的实现

Windows 上是通过注册表实现的

通过在 HKCR (HKEY_CALSSES_ROOT) 添加一条注册表记录

Win Registry

其中 command 的命令即为要执行的命令,注意后面要加一个参数 "%1"

Mac 上的实现

在应用里显示包内容,使用 xcode 查看 Info.plist 找到 URL types – URL Schemes 里添加一项

Mac Info.plist

Electron 的实现

1
app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [`${__dirname}`]);

这一句话即可完成 Windows 下和 macOS 下的协议注册,只不过需要应用启动后才可注册成功,就是说如果安装过后不打开的话,无法通过协议来唤醒应用,解决方式我们后面再讲

第一个参数为协议的名称, 第二个参数为执行的命令,第三个参数为所传字符串参数数组

在 Windows 环境下最后一项需要带上当前的项目路径,否则的话在开发模式下会打不开 electron 应用,打包完成后不会存在这个问题, mac 上也不会存在这个问题

Electron 上协议参数的处理

参数的处理分 2 中情况

  1. 新打开的窗口
  2. 打开的第二个实例

对于新打开的窗口:
使用 let argv = process.argv; 来获取进程参数,得到的是一个数组,如果做够一项包含我们的协议,则需要根据自己的字符串规则来进行处理

1
2
3
4
let argv = process.argv;
if (argv[argv.length - 1].indexOf(PROTOCOL + "://") > -1) {
//.....
}

对于打开的第二个实例:
windows 上监听的事件是 second-instance, mac 上监听的事件是 open-url, 2 个事件传入参数还不一样, Windows 下传入的参数是字符串数组,mac 传入的参数是字符串,都包含了协议名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 当运行第二个实例时,主动对焦
if (win) {
if (win.isMinimized()) win.restore();
win.focus();
win.show();

let message = handleArgv(commandLine);
processSend(message);
}
});

// macOS
app.on('open-url', (event, urlStr) => {
if (win) {
win.showInactive();
let message = handleArgv(urlStr);
processSend(message);
} else {
global.shareObject.message = handleArgv(urlStr);
global.shareObject.isSend = true;
}

});


function processSend(message) {
global.shareObject.message = message;
win.webContents.send('ch-1', 'send');
}

function handleArgv(argv) {
let urlObj = [];
if (Array.isArray(argv)) {
urlObj = argv[argv.length - 1].replace(PROTOCOL + "://", "").split("_");
} else {
urlObj = argv.replace(PROTOCOL + "://", "").split("_");
}
return urlObj.length >= 2 ? {sessionId: urlObj[0], url: urlObj[1], macInfo: os.networkInterfaces()} : {};
}

浏览器判断 scheme 协议是否存在

使用 setTimeout, 如果超时未打开的话则说明协议不存在

1
2
3
4
5
let downloadURL = "http://xxxx";
window.location = "joy-security://xxxxxx_xxxxxxx";
setTimeout(function() {
window.location = downloadURL;
},1000)

Electron 只启动一个实例

使用 app.requestSingleInstanceLock()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
app.quit()
} else {
app.on('ready', createWindow);

app.on('window-all-closed', () => {
app.quit();
});

app.on('activate', () => {
if (win == null) {
createWindow();
}
});
}

Electron 不显示菜单栏

经过实测
Menu.setApplicationMenu(null);
在 Windows 环境下没有菜单栏, 在 MAC 系统上开发模式下有菜单栏

正确的解决方式是
Menu.setApplicationMenu(Menu.buildFromTemplate([]));

注册快捷键

electron 自带的注册快捷键的功能函数是 globalShortcut, 这个是全局的快捷键,就是说焦点不在当前程序上也能触发快捷键
我这里使用的是一个第三方的组件 electron-localshortcut

1
2
3
4
5
6
7
electronLocalshortcut.register(win, 'F12', function () {
win.webContents.isDevToolsOpened() ? win.webContents.closeDevTools() : win.webContents.openDevTools();
});

electronLocalshortcut.register(win, 'F5', function () {
win.reload();
});

主线程和渲染线程之间的通信

这里使用的是 ipcMain 和 ipcRenderer
渲染进程使用ipcRenderer.send发送异步消息,然后使用on事件监控主进程的返回值。主进程使用on事件监听消息,使用event.sender.send返回数据

App.js:

1
2
3
4
5
const {ipcRenderer} = require('electron')
ipcRenderer.send('asynchronous-message', 'ping')
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})

main.js

1
2
3
4
5
const {ipcMain} = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.sender.send('asynchronous-reply', 'pong')
});

渲染进程使用ipcRenderer.sendSync发送同步消息。主进程使用on事件监控消息,使用event.returnValue返回数据给渲染进程。返回值在渲染进程中,就直接体现为ipcRenderer.sendSync的函数返回值

主线程如何给渲染线程发送消息

上面的示例没有说主线程如何对小渲染线程发送消息,应该这样做:

1
win.webContents.send('ch-1', 'send');

渲染进程和渲染进程如何互发消息

  1. 渲染进程的页面自己处理
  2. 通过主线程进行中间转换

渲染线程如何使用 electron 的功能

渲染窗口添加配置:

1
2
3
4
webPreferences: {
nodeIntegration: true, // 开启 node 功能
preload: path.join(__dirname, './public/renderer.js')
}

添加 renderer.js

1
global.electron = require('electron')

渲染进程的页面使用:

1
2
const electron = window.electron;
electron.xxxx

主线程和渲染进程如何共享对象

不需要引入任何包,直接在主线程使用 global

1
2
3
4
// 共享对象
global.shareObject = {
osInfo: os
};

渲染进程获取信息: let osInfo = electron.remote.getGlobal(‘shareObject’).osInfo;

主线程修改对象: global.shareObject.osInfo = message;

渲染线程修改对象: electron.remote.getGlobal(‘shareObject’).osInfo = null;

区分开发模式还是生产模式

建议使用 app.isPackaged

通过协议打开第二个实例的情况下触发的事件

Windows 环境下:

1
2
3
4
5
6
7
8
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 当运行第二个实例时,主动对焦
if (win) {
if (win.isMinimized()) win.restore();
win.focus();
win.show();
}
});

Mac 环境下:

1
2
3
4
5
6
// macOS
app.on('open-url', (event, urlStr) => {
if (win) {
win.showInactive();
}
});

开发环境和生成环境加载不同的页面

1
2
3
4
5
 if (app.isPackaged) {
win.loadURL(`file://${__dirname}/build/index.html`);
} else {
win.loadURL('http://localhost:3000');
}

添加多页面配置

  1. npm run eject
  2. 修改 webpack.config.js

entry 修改:
这里我加了一个 update.html 页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
entry: {
index: [
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
].filter(Boolean),
update: [
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appSrc + '/update.js',
].filter(Boolean),
},

output 修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
output: {
// The build folder.
path: isEnvProduction ? paths.appBuild : undefined,
// Add /* filename */ comments to generated require()s in the output.
pathinfo: isEnvDevelopment,
// There will be one main bundle, and one file per asynchronous chunk.
// In development, it does not produce real files.
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/[name]bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: isEnvProduction
? 'static/js/[name].[contenthash:8].chunk.js'
: isEnvDevelopment && 'static/js/[name].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
// We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: isEnvProduction
? info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/')
: isEnvDevelopment &&
(info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')),
},

注意修改其中的 filename

HtmlWebpackPlugin 修改:
新增一个 HtmlWebpackPlugin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
chunks: ["index"]
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
chunks: ["update"],
filename: "update.html"
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),

在 public 目录里添加 update.html, 内容照抄 index.html 文件即可;
在 src 目录下添加 update.js 文件:

1
2
3
4
5
6
7
8
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Update from './page/Update';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<Update />, document.getElementById('root'));
serviceWorker.register();

之后, http://localhost:3000/update.html 即可访问; 如果想加个路径,直接修改 HtmlWebpackPlugin 里的 filename, 例如 filename: "index/update.html"
就可以 使用 http://localhost:3000/index/update.html 来访问

引入 src 目录以外的文件报错

例如需要引入 public 目录下的图片,就会报错,此时,注释掉

1
// new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),

这一行,重启即可.

步骤

创建 create-react-app-antd 项目

  1. git clone https://github.com/ant-design/create-react-app-antd
  2. npm install
  3. 将 webpack 所有内建的配置暴露出来, npm run eject, 如果发现错误,看下 package.json 里 eject 的脚本是不是为 react-scripts eject
  4. 修改 config-overrides.js
1
2
3
module.exports = function override(config, env) {
return config;
};
  1. 修改 webpack.config.js 里的 module.rules.oneOf 支持 css 和 less, 添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    {
    test: /\.(css|less)$/,
    use: [
    require.resolve('style-loader'),
    {
    loader: require.resolve('css-loader'),
    options: {
    importLoaders: 1,
    },
    },
    {
    loader: require.resolve('postcss-loader'),
    options: {
    // Necessary for external CSS imports to work
    // https://github.com/facebookincubator/create-react-app/issues/2677
    ident: 'postcss',
    plugins: () => [
    require('postcss-flexbugs-fixes'),
    autoprefixer({
    browsers: [
    '>1%',
    'last 4 versions',
    'Firefox ESR',
    'not ie < 9', // React doesn't support IE8 anyway
    ],
    flexbox: 'no-2009',
    }),
    ],
    },
    },
    {
    loader: require.resolve('less-loader'),
    options: { javascriptEnabled: true }
    },
    ],
    }
  2. 修改 start.js 注释掉下面代码关闭项目启动自动打开浏览器

1
// openBrowser(urls.localUrlForBrowser);
  1. package.json 添加 "homepage": "." ,防止打包后的静态文件 index.html 引入 css 和 js 的路径错误

  2. App.less 修改为 @import '~antd/dist/antd.less';

添加 electron

  1. package.json 添加 "main": "main.js", 和 electron 依赖
1
2
3
4
5
6
{
"main": "main.js",
"devDependencies": {
"electron": "^6.0.7"
}
}
  1. 创建 main.js,添加以下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const {app, BrowserWindow, Menu} = require('electron');

let win;

let windowConfig = {
width: 800,
height: 600,
title: "Joy Security",
};

let menuTemplate = [{
label: 'Joy Security',
submenu: [{
label: '退出',
role: 'quit'
}, {
label: `关于 ${windowConfig.title}`,
role: 'about'
}]
}];


app.on('ready', createWindow);

app.on('window-all-closed', () => {
app.quit();
});

app.on('activate', () => {
if (win == null) {
createWindow();
}
});


function createWindow() {
// 隐藏菜单栏,兼容 MAC
Menu.setApplicationMenu(Menu.buildFromTemplate([]));

win = new BrowserWindow(windowConfig);

win.loadURL('http://localhost:3000');

win.on('close', () => {
//回收BrowserWindow对象
win = null;
});

win.on('resize', () => {
// win.reload();
});

}

  1. package.json 更改脚本
1
2
3
4
5
6
7
{
"scripts": {
"react-start": "node scripts/start.js",
"eletron-start": "electron .",
"react-build": "node scripts/build.js",
}
}
  1. 启动时先 react-start 再 eletron-start 即可看到效果

背景

在群晖的 Docker 组件里添加了个人的私有仓库,发现却无法下载镜像….

分析

在 Docker 组件里添加新的仓库,并设置为使用仓库,发现在仓库里下载镜像总是失败,状态栏提示查看日志,可是在日志里总看不到东西

想了想,可能是新添加的 docker 私服是 http 的服务,而不是 https

方法

  1. 于是我使用 GateOne 组件进入 shell
  2. 使用命令 docker pull xxx:xxx, 发现报错 Get https://172.18.18.90:5000/v2/: http: server gave HTTP response to HTTPS client , 果然是这个问题
  3. 于是找到 Docker 组件的配置文件目录,在 /var/packages/Docker/etc 目录下,添加配置文件 daemon.json
1
2
3
{
"insecure-registries": ["domain:5000"]
}
  1. 重启 Docker 组件, 发现不起作用,在命令行下 pull 依然报错,可想配置文件错了
  2. 转眼看到一个可疑的配置文件 dockerd.json, 里面已经有一些配置了,于是就把配置写到这个里面
  3. 再重启,问题解决.可见群晖对于 docker 是做了一些改变的.

添加环境变量【Registry】

1
2
[Registry]
Root: HKCR; Subkey: "JOY-SECURITY"; ValueType: string; ValueData: "URL:JOY-SECURITY Protocol Handler"; Flags: uninsdeletekey

Root (必需的)

根键。必须是下列值中的一个:

HKCR (HKEY_CLASSES_ROOT)
HKCU (HKEY_CURRENT_USER)
HKLM (HKEY_LOCAL_MACHINE)
HKU (HKEY_USERS)
HKCC (HKEY_CURRENT_CONFIG)

Subkey (必需的)

子键名,可以包含常量。

ValueType

值的数据类型。必须是下面中的一个:

none
string
expandsz
multisz
dword
qword
binary

如果指定了 none (默认设置),安装程序将创建一个没有键值的键,在这种情况下,ValueData 参数将被忽略。
如果指定了 string,安装程序将创建一个字符串 (REG_SZ) 值。
如果指定了 expandsz,安装程序将创建一个扩展字符串 (REG_EXPAND_SZ) 值。
如果指定了 multisz,安装程序将创建一个多行文本 (REG_MULTI_SZ) 值。
如果指定了 dword,安装程序将创建一个32位整数 (REG_DWORD) 值。
如果指定了 qdword,安装程序将创建一个64位整数 (REG_QDWORD) 值。
如果指定了 binary,安装程序将创建一个二进制 (REG_BINARY) 值。

Flags

这个参数是额外选项设置。多个选项可以使用空格隔开。支持下面的选项:

createvalueifdoesntexist
当指定了这个标记,安装程序只在如果没有相同名字的值存在时创建值。如果值类型是 none,或如果你指定了 deletevalue 标记,这个标记无效。

deletekey
当指定了这个标记,安装程序在如果条目存在的情况下,先将尝试删除它,包括其中的所有值和子键。如果 ValueType 不是 none,那么它将创建一个新的键和值。

要防止意外,如果 Subkey 是空白的或只包含反斜框符号,安装时这个标记被忽略。

deletevalue
当指定了这个标记,安装程序在如果值存在的情况下,先将尝试删除值,如果 ValueType 是 none,那么在键不存在的情况下,它将创建键以及新值。

dontcreatekey
当指定了这个标记,如果键已经在用户系统中不存在,安装程序将不尝试创建键或值。如果键不存在,不显示错误消息。

一般来说,这个键与 uninsdeletekey 标记组合使用,在卸载时删除键,但安装时不创建键。

noerror
如果安装程序因任何原因创建键或值失败,不显示错误消息。

preservestringtype
这只在当 ValueType 参数是 string 或 expandsz 时适用。当指定这个标记,并且值不存在或现有的值不是 string 类型 (REG_SZ 或 REG_EXPAND_SZ),它将用 ValueType 指定的类型创建。如果值存在,并且是 string 类型,它将用先存在值的相同值类型替换。

uninsclearvalue
当卸载程序时,设置值数据为空字符 (类型 REG_SZ)。这个标记不能与 uninsdeletekey 标记组合使用。

uninsdeletekey
当卸载程序时,删除整个键,包含其中的所有值和子键。这对于 Windows 自身使用的键明显不是一个好方法。你只能用于你的应用程序特有的键中。

为防止意外,安装期间如果 Subkey 空白或只包含反斜框符号,这个标记被忽略。

uninsdeletekeyifempty
当程序卸载时,如果这个键的内部没有值或子键,则删除这个键。这个标记可以与 uninsdeletevalue 组合使用

为防止意外,安装期间如果 Subkey 空白或只包含反斜框符号,这个标记被忽略。

uninsdeletevalue
当程序卸载时删除该值。这个标记不能与 uninsdeletekeyifempty 组合使用

注意: 在早于 1.1 的 Inno Setup 版本中,你可以使用这个标记连同数据类型 none,那么它的功能与“如果空则删除键”标记一样。这个方法已经不支持了。你必须使用 uninsdeletekeyifempty 标记实现。

添加环境变量【Code】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//添加环境变量
procedure CurStepChanged(CurStep: TSetupStep);
var
oldpath: String;
newpath: String;
ErrorCode: Integer;
begin
if CurStep = ssPostInstall then
begin
RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', oldPath);
newPath := oldPath + ';%JAVA_HOME%\bin\;';
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'PATH', newPath);
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'JAVA_HOME', ExpandConstant('{app}\java\jdk1.8.0_45'));
end;
end;

添加环境变量后记得在 setup 中配置 ChangesEnvironment=yes 通知其他应用程序从注册表重新获取环境变量

删除环境变量【Code】

1
2
3
4
5
6
7
8
9
10
11
12
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
var
oldpath: String;
newpath: String;
begin
if CurUninstallStep = usDone then
RegDeleteValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'JAVA_HOME');
RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', oldPath);
StringChangeEx(oldPath, ';%JAVA_HOME%\bin\;', '', True);
newPath := oldPath;
RegWriteStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'PATH', newPath);
end;

安装完成后执行脚本

1
2
[Run]
Filename: "{app}\service-install.bat"; Description: "{cm:LaunchProgram,{#StringChange('SERVICE_INSTALL', '&', '&&')}}"; Flags: shellexec postinstall waituntilterminated runascurrentuser

Parameters

程序的可选命令行参数,可以包含常量。

Flags

这个参数是额外选项设置。多个选项可以使用空格隔开。支持下面的选项:

32bit
Causes the {sys} constant to map to the 32-bit System directory when used in the Filename and WorkingDir parameters. This is the default behavior in a 32-bit mode install。

这个标记不能与 shellexec 组合使用。

64bit
Causes the {sys} constant to map to the 64-bit System directory when used in the Filename and WorkingDir parameters. This is the default behavior in a 64-bit mode install。

This flag can only be used when Setup is running on 64-bit Windows, otherwise an error will occur. On an installation supporting both 32- and 64-bit architectures, it is possible to avoid the error by adding a Check: IsWin64 parameter, which will cause the entry to be silently skipped when running on 32-bit Windows。

这个标记不能与 shellexec 组合使用。

hidewizard
如果指定了这个标记,向导将在程序运行期间隐藏。

nowait
如果指定了这个标记,它将在处理下一个 [Run] 条目前或完成安装前不等待进程执行完成。不能与 waituntilidle 或 waituntilterminated 组合使用。

postinstall
仅在 [Run] 段有效。告诉安装程序在安装完成向导页创建一个选择框,用户可以选中或不选中这个选择框从而决定是否处理这个条目。以前这个标记调用 showcheckbox。

如果安装程序已经重新启动了用户的电脑 (安装了一个带 restartreplace 标记的文件或如果 [Setup] 段的 AlwaysRestart 指令是 yes 引起的),选择框没有机会出现,因此这些条目不会被处理。

[Files] 段条目中的 isreadme 标记现在已被废弃。如果编译器带 isreadme 标记的条目,它将从 [Files] 段条目中忽略这个标记,并在 [Run] 段条目列表的开头插入一个生成的 [Run] 条目。这相生成的 [Run] 段条目运行自述文件,并带有 shellexec,skipifdoesntexist,postinstall 和 skipifsilent 标记。

runascurrentuser
如果指定了这个标记,the spawned process will inherit Setup/Uninstall’s user credentials (typically, full administrative privileges)。

This is the default behavior when the postinstall flag is not used。

这个标记不能与 runasoriginaluser 组合使用。

runasoriginaluser
仅在 [Run] 段有效。If this flag is specified and the system is running Windows Vista or later, the spawned process will execute with the (normally non-elevated) credentials of the user that started Setup initially (i.e., the “pre-UAC dialog” credentials)。

This is the default behavior when the postinstall flag is used。

If a user launches Setup by right-clicking its EXE file and selecting “Run as administrator”, then this flag, unfortunately, will have no effect, because Setup has no opportunity to run any code with the original user credentials. The same is true if Setup is launched from an already-elevated process. Note, however, that this is not an Inno Setup-specific limitation; Windows Installer-based installers cannot return to the original user credentials either in such cases。

这个标记不能与 runascurrentuser 组合使用。

runhidden
如果指定了这个标记,它将在隐藏窗口中运行程序。请在执行一个要提示用户输入的程序中不要使用这个标记。

runmaximized
如果指定了这个标记,将在最大化窗口运行程序或文档。

runminimized
如果指定了这个标记,将在最小化窗口运行程序或文档。

shellexec
如果 Filename 不是一个直接可执行文件 (.exe 或 .com 文件),这个标记是必需的。当设置这个标记时,Filename 可以是一个文件夹或任何已注册的文件类型 – 包括 .hlp,.doc 等。该文件将用用户系统中与这个文件类型关联的应用程序打开,与在资源管理器双击文件的方法是相同的。

按默认,当使用 shellexec 标记时,将不等待,直到生成的进程终止。
如果你需要,你必须添加标记 waituntilterminated。注意,如果新进程未生成,它不能执行也将不等待 – 例如,文件指定指定为一个文件夹。

skipifdoesntexist
如果这个标记在 [Run] 段中指定,如果 Filename 不存在,安装程序不显示错误消息。

如果这个标记在 [UninstallRun] 段中指定,如果 Filename 不存在,卸载程序不显示“一些元素不能删除”的警告。

在使用这个标记时, Filename 必须是一个绝对路径。

skipifnotsilent
仅在 [Run] 段有效。告诉安装程序如果安装程序未在后台运行则跳过这个条目。

skipifsilent
仅在 [Run] 段有效。告诉安装程序如果安装程序在后台运行则跳过这个条目。

unchecked
仅在 [Run] 段有效。告诉安装程序初始为不选中选择框。如果用户希望处理这个条目,可以通过选取选择框执行。如果 postinstall 标记未同时指定,这个标记被忽略。

waituntilidle
如果指定了这个标记,它将在未输入期间等待,直到进程等待用户输入,而不是等待进程终止。(调用 WaitForInputIdle Win32 函数。) 不能与 nowait 或 waituntilterminated 组合使用。

waituntilterminated
如果指定这个标记,将等待到进程完全终止。注意这是一个默认动作 (也就是你不需要指定这个标记),除非你使用了 shellexec 标记,在这种情况下,如果你要等待,需要指定这个标记。不能与 nowait 或 waituntilidle 组合使用。

安装前卸载旧版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function InitializeSetup(): boolean;
var
bRes: Boolean;
ResultStr: String;
ResultCode: Integer;
begin
if RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{4AA89D60-9EB2-4A69-B73E-67E3AC22CF8E}_is1', 'UninstallString', ResultStr) then
begin
MsgBox('检测到系统之前安装过本程序,即将卸载低版本!', mbInformation, MB_OK);
ResultStr := RemoveQuotes(ResultStr);
bRes := Exec(ResultStr, '/silent', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if bRes and (ResultCode = 0) then begin
result := true;
Exit;
end else
MsgBox('卸载低版本失败!', mbInformation, MB_OK);
result:= false;
Exit;
end;
result := true;
end;

检测服务是否存在并删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function DeleteService(strExeName: String): Boolean;
var
ErrorCode: Integer;
bRes: Boolean;
strCmdFind: String;
strCmdDelete: String;
begin
strCmdFind := Format('/c sc query "%s"', [strExeName]);
strCmdDelete := Format('/c sc stop "%s" & sc delete "%s"', [strExeName, strExeName]);
bRes := ShellExec('open', ExpandConstant('{cmd}'), strCmdFind, '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
if bRes and (ErrorCode = 0) then begin
if MsgBox('检测到 ' + strExeName + ' 服务存在,需要删除,是否继续?', mbConfirmation, MB_YESNO) = IDYES then begin
bRes := ShellExec('open', ExpandConstant('{cmd}'), strCmdDelete, '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
if bRes and (ErrorCode = 0) then begin
MsgBox('服务 '+strExeName+' 删除成功!', mbInformation, MB_OK);
result := true;
Exit;
end else
MsgBox('删除失败,请手动删除服务 ' + strExeName, mbError, MB_OK);
result := false;
Exit;
end else
result := false;
Exit;
end;
MsgBox('服务 '+strExeName+' 不存在!', mbInformation, MB_OK);
result := true;
end;

执行

执行 git config credential.helper store

或者在 .gitconfig 添加

1
2
[credential]
helper = store
0%