【问题标题】:Powershell variable scope and temporary files within the context of a VS2015 MSBuild projectVS2015 MSBuild 项目上下文中的 Powershell 变量范围和临时文件
【发布时间】:2016-01-11 15:25:58
【问题描述】:

问题

两个独立的 Powershell 脚本,从两个独立的项目(Chrome 和 Firefox)运行,显然写入同一个临时文件。

这种情况并不经常发生,显然每隔几个月左右发生一次。但是,当这种情况发生时,这两个单独的项目最终都会将它们的 manifest.json 和 package.json 文件混合在一起,这样每个文件中就会穿插来自另一个文件的随机行。

任何想法为什么会发生这种情况?

详情

我有一个 VS2015 解决方案,其中包含 25 个项目。其中一个项目称为“部署”,并已声明对多个其他项目的依赖关系。对这个问题有重要意义的两个依赖项是“ChromeExtension”和“FirefoxAddOn”项目。

每个项目都包含一个构建目标,用于在相应的清单文件中设置版本信息。这是 Chrome 目标:

<Target Name="SetVersion">
  <PropertyGroup>
    <PowerShellExe>powershell.exe</PowerShellExe>
    <PreBuildScript>$(SolutionDir)powershell\ChromePreBuild.ps1</PreBuildScript>
  </PropertyGroup>
  <Exec Command="$(PowerShellExe) -NonInteractive -ExecutionPolicy Unrestricted -command &quot;&amp; { &amp;'$(PreBuildScript)' -solutionDir '$(SolutionDir)\' }&quot;" />
</Target>

除了执行脚本FirefoxPreBuild.ps1之外,Firefox 目标是相同的。

脚本相对简单。 Chrome 脚本:

param($solutionDir)

. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"
. "${solutionDir}powershell\ReplaceName.ps1"
. "${solutionDir}powershell\ChromeOptions.ps1"

Write-Host "Performing pre-build actions for Chrome add-on $chromeExtensionVersion"

ReplaceVersion "${solutionDir}ChromeExtension\manifest.json" $chromeExtensionVersion
ReplaceName "${solutionDir}ChromeExtension\manifest.json" "$chromeExtensionName"

还有 Firefox 脚本:

param($solutionDir)

. "${solutionDir}powershell\BuildConfig.ps1"
. "${solutionDir}powershell\ReplaceVersion.ps1"

Write-Host "Performing pre-build actions for Firefox add-on $firefoxExtensionVersion"

ReplaceVersion "${solutionDir}FirefoxAddOn\package.json" $firefoxExtensionVersion

从两个脚本调用的 ReplaceVersion 函数非常丑陋,但可以完成工作:

function ReplaceVersion {
    $file = $args[0]
    $replacementVersion = $args[1]
    Write-Host "- Updating version number in $file to ""$replacementVersion"""
    $tmp = [System.IO.Path]::GetTempFileName();
    if(Test-Path $tmp -PathType Leaf) {
        Remove-Item $tmp
    }
    Get-Content $file | Foreach-Object -process {
        $line = $_
        if ( ($line -match 'AssemblyVersion.*(\d+\.\d+\.\d+\.\d+)') -or
             ($line -match 'AssemblyFileVersion.*(\d+\.\d+\.\d+\.\d+)') -or
             ($line -match 'AppFolderName.*(\d+\.\d+\.\d+\.\d+)') -or
             ($line -match 'SupportedSyncFeatureSetRevision\s*=\s*(\d+);') -or
             ($line -match 'this.AddinName = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
             ($line -match 'this.Text = ".* v(\d+\.\d+\.\d+\.\d+)";') -or
             ($line -match '"version":.*"(\d+\.\d+\.\d+\.?\d*)"') ) {
             $version = $matches[1]
             $line = $line -replace "$version", "$replacementVersion"
        }
        $line | Add-Content $tmp
    }
    #replace the old file with the new one
    Remove-Item -force $file
    Move-Item $tmp $file -Force -Confirm:$false
}

而ReplaceName 函数(仅限 Chrome)是,难以想象且非 DRY,完全一样的东西,除了要匹配不同的模式:

function ReplaceName {
    $file = $args[0]
    $replacementName = $args[1]
    Write-Host "- Updating extension name in $file to ""$replacementName"""
    $tmp = [System.IO.Path]::GetTempFileName();
    if(Test-Path $tmp -PathType Leaf) {
        Remove-Item $tmp
    }
    Get-Content $file | Foreach-Object -process {
        $line = $_
        if ( ($line -match '"name":.*"(.*)"') ) {
             $name = [regex]::escape($matches[1])
             $line = $line -replace "$name", "$replacementName"
        }
        $line | Add-Content $tmp
    }
    #replace the old file with the new one
    Remove-Item -force $file
    Move-Item $tmp $file -Force -Confirm:$false
}

【问题讨论】:

  • 从脚本中删除该部分if(Test-Path $tmp -PathType Leaf) { Remove-Item $tmp }。
  • 哈!谢谢!我敢打赌这就是原因。我不知道为什么会在那里。从文件的第一个版本开始就是这样,所以这行代码没有历史记录。我认为这意味着GetTempFileName() 确保基于Temp 目录的内容的唯一性,而不是基于任何外部来源?我将无法明确测试以确保这是解决方案,因为问题很少发生,而且我不知道如何强制它发生。但我很确定你是对的。如果你想写一个问题的答案,我会接受。

标签: powershell msbuild


【解决方案1】:

您应该从脚本中删除该部分:

if(Test-Path $tmp -PathType Leaf) {
    Remove-Item $tmp
}

如documentation中所述

在磁盘上创建一个唯一命名的零字节临时文件并返回该文件的完整路径。

[System.IO.Path]::GetTempFileName() 将为您创建空文件,因此您不必删除并重新创建它。

如果您查看[System.IO.Path]::GetTempFileName() 的source,那么您可以看到它是通过调用GetTempFileName WinAPI 函数来实现的,其中uUnique 等于0。这是文档的相关部分:

如果uUnique 为零,该函数会尝试使用当前系统时间形成一个唯一的文件名。如果文件已经存在,则数字加一并且函数测试该文件是否已经存在。这一直持续到找到唯一的文件名;该函数使用该名称创建一个文件并关闭它。

如您所见,GetTempFileName 使用文件存在作为信号来创建不同的文件名。通过删除文件,您打开后续调用将为临时文件返回相同文件名的可能性。

【讨论】:

    猜你喜欢
    • 2017-06-26
    • 1970-01-01
    • 2013-09-18
    • 2012-03-08
    • 2011-04-07
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多