I realize that the build/revision number of the assembly can be auto incremented by changing
[assembly: AssemblyVersion("1.0.0.0")]
to
[assembly: AssemblyVersion("1.0.*")]
in the AssemblyInfo.cs file.
But how do I auto-increment the version number defined in Package.appxmanifest? That is, the version number accessible through:
Windows.ApplicationModel.Package.Current.Id.Version
I'm using Visual Studio 2013.
Three liner, versioning by date
I ran into that issue until I figured out after a lot of research how to achieve automatic versioning in just three line in the .csproj file. Here it is:
<Target Name="NugetPackAutoVersioning" AfterTargets="Build">
<Exec Command="dotnet pack -p:PackageVersion=$([System.DateTime]::Now.ToString("yyyy.MM.dd.HHmm")) --no-build --configuration $(Configuration) --output "$(SolutionDir)nuget"" />
</Target>
This will output a NuGet package named like {ProjectName}.{Year}.{Month}.{Day}.{Hour}{Minute} in a "nuget" folder at the project root, guaranteeing that packages built later are versioned as posteriors.
In your .csproj file, you should add a property named AppxAutoIncrementPackageRevision with the value set to True.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
...
<AppxAutoIncrementPackageRevision>True</AppxAutoIncrementPackageRevision>
...
</PropertyGroup>
This will auto-increment the appx package version every time you build it through Visual Studio.
dotnet pack wasn't including runtime/build targets in my package, so I prefer to use the Generate package on build package option in the project.
I edited my project file and added the following property inside the <PropertyGroup>
<PackageVersion>$([System.DateTime]::Now.ToString("yyyy.MM.dd.HHmmss"))/PackageVersion>
In Visual Studio 2017, I created a PowerShell script to pull the package id and version number information by looking in a few places and update the .csproj file if needed.
The help comment in the file describes how to call it from your .csproj during a build (and build the NuGet package as part of the build):
<#
.SYNOPSIS
Update version information in the .csproj file in preparation for building a nuget
package.
.DESCRIPTION
Discovers the package name and latest version. If that package exists and is newer
than the target that goes into it, do nothing; otherwise, increment the version
information in the .csproj file (without updating that .csproj file last modified
time).
The latest version gets picked from the maximum of the package/file/assembly
versions in the .csproj file and the version found on the nuget server.
.PARAMETER csproj
The path to the .csproj file to check
.PARAMETER target
The path to the build target (the DLL) that goes into the package. Used to decide whether to
increment the version or not.
.PARAMETER packageDir
The location packages end up.
.PARAMETER nugetSite
The domain name or IP address of the nuget server to query for package version information.
.EXAMPLE
To build a nuget package on every build, add this to the csproj file:
<Project Sdk="Microsoft.NET.Sdk">
<Target Name="PostcompileScript" AfterTargets="Build">
<Exec Command="powershell.exe -NonInteractive -ExecutionPolicy Unrestricted -noexit -file "$(SolutionDir)UpdateCsprojPackageVersion.ps1" -csproj "$(ProjectPath)" -target "$(TargetPath)" -packageDir "$(SolutionDir)nuget"" />
<Exec Command="dotnet pack --no-build --include-symbols --include-source --configuration $(Configuration) --output "$(SolutionDir)nuget" />
</Target>
</Project>
#>
param (
[Parameter(Mandatory=$true)][string]$csproj,
[Parameter(Mandatory=$true)][string]$target,
[Parameter(Mandatory=$true)][string]$packageDir,
[string]$nugetSite = "local-nuget-server"
)
$csproj = $csproj.Trim()
Write-Output "Increment package/file/assembly version in $csproj"
function ParseVersion($version)
{
$major = 0
$minor = 1
$build = 0
$revisionType = 'alpha'
$revision = 0
$gotData = $false
$m = [regex]::Match($version, '(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z]*)(\d*)|\.(\d+))?')
if ($m.Success)
{
$major = $m.Groups[1].Value -as [int]
$minor = $m.Groups[2].Value -as [int]
$build = $m.Groups[3].Value -as [int]
if ($m.Groups[4].Success)
{
$revisionType = $m.Groups[4].Value.ToLower()
$revision = $m.Groups[5].Value -as [int]
}
else
{
$revisionType = ''
if ($m.Groups[6].Success)
{
$revision = $m.Groups[6].Value
}
}
}
return [Convert]::ToInt32($major, 10), [Convert]::ToInt32($minor, 10), [Convert]::ToInt32($build, 10), $revisionType, [Convert]::ToInt32($revision, 10)
}
function VersionGreaterOrEqual($major1, $minor1, $build1, $revision1, $major2, $minor2, $build2, $revision2)
{
return ($major1 -gt $major2 -or ($major1 -eq $major2 -and ($minor1 -gt $minor2 -or ($minor1 -eq $minor2 -and ($build1 -gt $build2 -or ($build1 -eq $build2 -and $revision1 -ge $revision2))))))
}
# Read csproj (XML)
$xml = New-Object -TypeName XML
$xml.Load($csproj)
$project = $xml.SelectSingleNode("/Project")
if ($project -eq $null)
{
$project = $xml.CreateElement("Project")
$xml.AppendChild($project)
}
$propertyGroup = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]")
if ($propertyGroup -eq $null)
{
$propertyGroup = $project.AppendChild($xml.CreateElement("PropertyGroup"))
}
# Look for the package identifier in various places in the project file, as a last resort, use the project file name.
$packageId = $null
$packageidFrom = "PackageId in csproj"
$packageIdNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]/PackageId")
if ($packageIdNode -ne $null)
{
$packageId = $packageIdNode.'#text'
}
if ([String]::IsNullOrWhiteSpace($packageId))
{
$assemblyTitle = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]/AssemblyTitle")
if ($assemblyTitle -ne $null)
{
$packageId = $assemblyTitle.'#text'
$packageidFrom = "AssemblyTitle in csproj"
}
if ([String]::IsNullOrWhiteSpace($packageId))
{
$assemblyName = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]/AssemblyName")
if ($assemblyName -ne $null)
{
$packageId = $assemblyName.'#text'
$packageidFrom = "AssemblyName in csproj"
}
if ([String]::IsNullOrWhiteSpace($packageId))
{
$title = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]/Title")
if ($title -ne $null)
{
$packageId = $title.'#text'
$packageidFrom = "Title in csproj"
}
if ([String]::IsNullOrWhiteSpace($packageId))
{
$packageId = (New-Object System.IO.FileInfo($csproj)).BaseName
$packageidFrom = "file name of csproj"
if ($title -eq $null)
{
$title = $propertyGroup.AppendChild($xml.CreateElement("Title"))
}
$title.'#text' = $packageId
}
if ($assemblyName -eq $null)
{
$assemblyName = $propertyGroup.AppendChild($xml.CreateElement("AssemblyName"))
}
$assemblyName.'#text' = $packageId
}
if ($assemblyTitle -eq $null)
{
$assemblyTitle = $propertyGroup.AppendChild($xml.CreateElement("AssemblyTitle"))
}
$assemblyTitle.'#text' = $packageId
}
if ($packageIdNode -eq $null)
{
$packageIdNode = $propertyGroup.AppendChild($xml.CreateElement("PackageId"))
}
$packageIdNode.'#text' = $packageId;
}
Write-Output " Found Package Identifier ""$packageId"" from $packageIdFrom"
# Get the latest version from the nuget server.
# The query comes from running nuget.exe with the -Verbose option (and guessing that the search term can be a regular expression).
# The response comes back as XML
$nugetXml = New-Object -TypeName XML
$nugetXml.Load("http://$nugetSite/api/v2/Search()?`$filter=IsAbsoluteLatestVersion&searchTerm=%27^$packageId$%27&targetFramework=%27%27&includePrerelease=true")
$nugetVersionNode = $nugetXml.SelectSingleNode("feed.entry.properties.Version")
$nugetVersion = ''
if ($nugetVersionNode -ne $null)
{
$nugetVersion = $nugetVersionNode.'#text'
}
# Retrieve Version Nodes
$packageVersionNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]/PackageVersion")
if ($packageVersionNode -eq $null) {
$packageVersionNode = $propertyGroup.AppendChild($xml.CreateElement("PackageVersion"))
}
$assemblyVersionNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]/AssemblyVersion")
if ($assemblyVersionNode -eq $null) {
$assemblyVersionNode = $propertyGroup.AppendChild($xml.CreateElement("AssemblyVersion"))
}
$fileVersionNode = $xml.SelectSingleNode("/Project/PropertyGroup[not(#*)]/FileVersion")
if ($fileVersionNode -eq $null) {
$fileVersionNode = $propertyGroup.AppendChild($xml.CreateElement("FileVersion"))
}
$packageVersion = $packageVersionNode.'#text'
$assemblyVersion = $assemblyVersionNode.'#text'
$fileVersion = $fileVersionNode.'#text'
Write-Output " Read versions: qat-nuget=""$nugetVersion"", package=""$packageVersion"", file=""$fileVersion"", assembly=""$assemblyVersion"""
# Split the Version Numbers
$major, $minor, $build, $revisionType, $revision = ParseVersion $nugetVersion
$paMajor, $paMinor, $paBuild, $paRevisionType, $paRevision = ParseVersion $packageVersion
$avMajor, $avMinor, $avBuild, $avRevisionType, $avRevision = ParseVersion $assemblyVersion
$fvMajor, $fvMinor, $fvBuild, $fvRevisionType, $fvRevision = ParseVersion $fileVersion
# choose between qat-nuget's package version and the package version found in the project file
if ((VersionGreaterOrEqual $paMajor $paMinor $paBuild 0 $major $minor $build 0) -and (($paRevisionType -eq '' -or $paRevisionType -gt $revisionType) -or ($paRevisionType -eq $revisionType -and $paRevision -gt $revision)))
{
$major = $paMajor
$minor = $paMinor
$build = $paBuild
$revisionType = $paRevisionType
$revision = $paRevision
}
# Because of the way the build works, the file and assembly versions being set are for the
# _next_ build, the package version is for the _current_ build. We err on the side of the
# package version - that is, the file and assembly version may not increment at edge cases.
# If you want to be sure that all the versions are the same in a package, you must build
# twice.
# To keep revisions for file and assembly alpha/beta/rc/release builds going in order, we
# give the different releaseType values different base revision values.
switch($revisionType.ToLower())
{
"rc" { $revisionDelta = 20001 }
"beta" { $revisionDelta = 10001 }
"alpha" { $revisionDelta = 1 }
default { $revisionDelta = 40001 } # for release revisions
}
# Boost the version to the assembly version or the file version value if those are greater
if ((VersionGreaterOrEqual $avMajor $avMinor $avBuild $avRevision $major $minor $build ($revision + $revisionDelta)) -and (VersionGreaterOrEqual $avMajor $avMinor $avBuild $avRevision $fvMajor $fvMinor $fvBuild $fvRevision))
{
$major = $avMajor
$minor = $avMinor
$build = $avBuild
$revision = $avRevision - $revisionDelta
}
elseif (VersionGreaterOrEqual $fvMajor $fvMinor $fvBuild $fvRevision $major $minor $build ($revision + $revisionDelta))
{
$major = $fvMajor
$minor = $fvMinor
$build = $fvBuild
$revision = $fvRevision - $revisionDelta
}
if ($revision -lt 0)
{
$revision -eq 0
}
$fileAssemblyRevision = $revision + $revisionDelta
$fileAssemblyBuild = $build
if ($revisionType -ne "")
{
$oldPackageName = "$packageId.$major.$minor.$build-$revisionType$revision.nupkg"
}
else
{
$oldPackageName = "$packageId.$major.$minor.$build.nupkg"
}
$oldPackage = [System.IO.Path]::Combine($packageDir, $oldPackageName)
if ([System.IO.File]::Exists($oldPackage) -and [System.IO.File]::GetLastWriteTime($oldPackage) -ge [System.IO.File]::GetLastWriteTime($target))
{
$targetName = [System.IO.Path]::GetFileName($target)
Write-Output " * Not incrementing version - $oldPackageName newer than $targetName"
}
else
{
# Increment revision or build
if ($revisionType -ne "")
{
$fileAssemblyRevision = $fileAssemblyRevision + 1
$revision = $revision + 1
}
else
{
$fileAssemblyBuild = $fileAssemblyBuild + 1
$build = $build + 1
$fileAssemblyRevision = 0
$revision = $revision + 0
}
# Put the incremented version into the csproj file and save it
$fileAssemblyVersion = "$major.$minor.$fileAssemblyBuild.$fileAssemblyRevision"
$assemblyVersionNode.RemoveAll()
$dummy = $assemblyVersionNode.AppendChild($xml.CreateTextNode($fileAssemblyVersion))
$fileVersionNode.RemoveAll()
$dummy = $fileVersionNode.AppendChild($xml.CreateTextNode($fileAssemblyVersion))
$packageVersionNode.RemoveAll()
if ($revisionType -eq '')
{
$packageVersion = "$major.$minor.$build"
}
else
{
$packageVersion = "$major.$minor.$build-$revisionType$revision"
}
$dummy = $packageVersionNode.AppendChild($xml.CreateTextNode($packageVersion))
Write-Output " Set file/assembly version to $fileAssemblyVersion, package version to $packageVersion"
$lastWriteTime = [System.IO.File]::GetLastWriteTime($csproj)
$xml.Save($csproj)
[System.IO.File]::SetLastWriteTime($csproj, $lastWriteTime)
}
This scripts enforces the questionable practice of keeping the file/assembly/package version numbers in sync - we have found that practice useful. To accomplish this, revision numbers need special handling. A delta is given for beta, release candidate, and release so version numbers don't decrease when moving from package alpha → beta, etc.
There is a trick in there because the writing of the project file comes after the build. This means the file and assembly version numbers must be kept one increment behind the package version number (the package gets built after the increment).
This script assumes you have a NuGet server to query. It shouldn't be hard to chop that code out if you don't have one.
Whenever you create a package there is an option for this functionality, and here is a screenshot for that. All you have to do is check the Automatically increment option.
From Visual Studio, navigate like menu Project → Store → Create App Packages → Automatically increment
Related
I am using GitVersion to version my C#.NET application. My application also has a -V option, to show the current version of the binary.
How can I get data from GitVersion into my application, so that it is updated each time I build?
I got it using a combination of a PowerShell script and a pre-build event:
The script is as follows (saved as gitversion.ps1 in the project dir:
$gitVersionJson = dotnet gitversion /output json
$By = [System.Text.Encoding]::Unicode.GetBytes($gitVersionJson)
$output =[Convert]::ToBase64String($By)
"using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
class GitVersion {
private static Dictionary<string, object> _values;
private static Dictionary<string, object> Values {
get {
if (_values == null) {
byte[] data = Convert.FromBase64String(""$output"");
string decodedString = Encoding.Unicode.GetString(data);
_values = JsonSerializer.Deserialize<Dictionary<string, object>>(decodedString);
}
return _values;
}
}
public static object MajorMinorPatch {
get {
return Values[""MajorMinorPatch""];
}
}
}
" | Out-File GitVersion.cs
"Generated GitVersion.cs" | Write-Output
Then in as a pre-build event, I added this in the Build settings:
powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File "$(ProjectDir)gitversion.ps1"
Or in myproject.csproj:
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="powershell -ExecutionPolicy Bypass -NoProfile -NonInteractive -File "$(ProjectDir)gitversion.ps1" " />
</Target>
This will create a GitVersion class, you can use in your code:
Console.WriteLine(GitVersion.MajorMinorPatch);
I would like to run a powershell build script where I have some config files (xml/json) that are not app.config, appsettings.json nor web.config files that I would like to transform based on the build configuration. The perfect tool for this appears to be VisualStudio.SlowCheetah since it supports both xml and json and it uses the same underlying technology as web.config transforms (which are also in my project). Is there any way to run this tool from powershell, it would be nice to have the same tool that does the transforms within the solution also do transforms on my auxiliary files?
So here is my proof of concept:
My folder contains 4 files:
PerformTransform.ps1 - Stand-in for my build script that will initiate the transform
Transform-Config.ps1 - Scripts which use SlowCheetah to perform transforms
Sample.config - A sample config file
Sample.Prod.config - A sample xml transform file
PerformTransform.ps1 looks like:
cls
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
# Temporarily adds the script folder to the path
# so that the Transform-Config command is available
if(($env:Path -split ';') -notcontains $scriptPath) {
$env:Path += ';' + $scriptPath
}
Transform-Config "$scriptPath\Sample.config" "$scriptPath\Sample.Prod.config" "$scriptPath\Sample.Transformed.config"
Here is my Transform-Config.ps1:
#!/usr/bin/env powershell
<#
.SYNOPSIS
You can use this script to easly transform any XML file using XDT or JSON file using JDT.
To use this script you can just save it locally and execute it. The script
will download its dependencies automatically.
#>
[cmdletbinding()]
param(
[Parameter(
Mandatory=$true,
Position=0)]
$sourceFile,
[Parameter(
Mandatory=$true,
Position=1)]
$transformFile,
[Parameter(
Mandatory=$true,
Position=2)]
$destFile
)
$loggingStubSource = #"
using System;
namespace Microsoft.VisualStudio.SlowCheetah
{
public class LoggingStub : ITransformationLogger
{
public void LogError(string message, params object[] messageArgs) { }
public void LogError(string file, int lineNumber, int linePosition, string message, params object[] messageArgs) { }
public void LogErrorFromException(Exception ex) { }
public void LogErrorFromException(Exception ex, string file, int lineNumber, int linePosition) { }
public void LogMessage(LogMessageImportance importance, string message, params object[] messageArgs) { }
public void LogWarning(string message, params object[] messageArgs) { }
public void LogWarning(string file, int lineNumber, int linePosition, string message, params object[] messageArgs) { }
}
}
"# # this here-string terminator needs to be at column zero
<#
.SYNOPSIS
If nuget is not in the tools
folder then it will be downloaded there.
#>
function Get-Nuget(){
[cmdletbinding()]
param(
$toolsDir = "$env:LOCALAPPDATA\NuGet\BuildTools\",
$nugetDownloadUrl = 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe'
)
process{
$nugetDestPath = Join-Path -Path $toolsDir -ChildPath nuget.exe
if(!(Test-Path $nugetDestPath)){
'Downloading nuget.exe' | Write-Verbose
# download nuget
$webclient = New-Object System.Net.WebClient
$webclient.DownloadFile($nugetDownloadUrl, $nugetDestPath)
# double check that is was written to disk
if(!(Test-Path $nugetDestPath)){
throw 'unable to download nuget'
}
}
# return the path of the file
$nugetDestPath
}
}
function Get-Nuget-Package(){
[cmdletbinding()]
param(
[Parameter(
Mandatory=$true,
Position=0)]
$packageName,
[Parameter(
Mandatory=$true,
Position=1)]
$toolFileName,
$toolsDir = "$env:LOCALAPPDATA\NuGet\BuildTools\",
$nugetDownloadUrl = 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe'
)
process{
if(!(Test-Path $toolsDir)){
New-Item -Path $toolsDir -ItemType Directory | Out-Null
}
$toolPath = (Get-ChildItem -Path $toolsDir -Include $toolFileName -Recurse) | Select-Object -First 1
if($toolPath){
return $toolPath
}
"Downloading package [$packageName] since it was not found in the tools folder [$toolsDir]" | Write-Verbose
$cmdArgs = #('install',$packageName,'-OutputDirectory',(Resolve-Path $toolsDir).ToString())
"Calling nuget.exe to download [$packageName] with the following args: [{0} {1}]" -f (Get-Nuget -toolsDir $toolsDir -nugetDownloadUrl $nugetDownloadUrl), ($cmdArgs -join ' ') | Write-Verbose
&(Get-Nuget -toolsDir $toolsDir -nugetDownloadUrl $nugetDownloadUrl) $cmdArgs | Out-Null
$toolPath = (Get-ChildItem -Path $toolsDir -Include $toolFileName -Recurse) | Select-Object -First 1
return $toolPath
}
}
function Transform-Config{
[cmdletbinding()]
param(
[Parameter(
Mandatory=$true,
Position=0)]
$sourceFile,
[Parameter(
Mandatory=$true,
Position=1)]
$transformFile,
[Parameter(
Mandatory=$true,
Position=2)]
$destFile,
$toolsDir = "$env:LOCALAPPDATA\NuGet\BuildTools\"
)
process{
$sourcePath = (Resolve-Path $sourceFile).ToString()
$transformPath = (Resolve-Path $transformFile).ToString()
$cheetahPath = Get-Nuget-Package -packageName 'Microsoft.VisualStudio.SlowCheetah' -toolFileName 'Microsoft.VisualStudio.SlowCheetah.dll' -toolsDir $toolsDir
if(!$cheetahPath){
throw ('Failed to download Slow Cheetah package')
}
if (-not ([System.Management.Automation.PSTypeName]'Microsoft.VisualStudio.SlowCheetah.LoggingStub').Type)
{
[Reflection.Assembly]::LoadFrom($cheetahPath.FullName) | Out-Null
Add-Type -TypeDefinition $loggingStubSource -Language CSharp -ReferencedAssemblies $cheetahPath.FullName
}
$logStub = New-Object Microsoft.VisualStudio.SlowCheetah.LoggingStub
$transformer = [Microsoft.VisualStudio.SlowCheetah.TransformerFactory]::GetTransformer($sourcePath, $logStub);
$success = $transformer.Transform($sourcePath, $transformPath, $destFile);
if(!$success){
throw ("Transform of file [] failed!!!!")
}
Write-Host "Transform successful."
}
}
Transform-Config -sourceFile $sourceFile -transformFile $transformFile -destFile $destFile
The config files are not important, you should be able to use an existing app.config and app.ENV.config transform file to play with this.
If there is an easier way to do this, please let me know!
I've been battling with this for a while now and can't find an explanation. I'm attempting to enable inheritance on a directory for audit rules. In this example, I'm setting the audit rule on c:\Program Files\Microsoft SQL Server\MSSQL12.NEWINSTANCE and propagating to all children. The directory c:\Program Files\Microsoft SQL Server\MSSQL12.NEWINSTANCE\MSSQL\Backup gets the audit rule via inheritance, but the Logs directory does not. Here's a snippet of the code I'm using to enable inheritance:
$Path = "C:\Program Files\Microsoft SQL Server\MSSQL12.NEWINSTANCE\MSSQL\Log"
[System.IO.DirectoryInfo]$Info = New-Object -TypeName System.IO.DirectoryInfo($Path)
[System.Security.AccessControl.DirectorySecurity]$Acl = $Info.GetAccessControl()
$Acl.SetAuditRuleProtection($false, $false)
$Info.SetAccessControl($Acl)
I've tried a number of combinations including Get-Acl, Set-Acl, (Get-Item -Path $Path).GetAccessControl(), etc. It appears I can disable inheritance and remove the rules, but not disable inheritance and preserve the existing rules (via modifying the parameters for SetAuditRuleProtection).
All of this works if I do it via the GUI, so I don't believe it's some issue with the directory or my permissions. Any ideas/thoughts would be welcome.
From what I grasp looking everywhere on the Net you can't reset audit inheritance unless you have an audit rule (it doesn't handle well a $null value). So in your case it should look like:
# Temporary audit rule just to make sure it is not null. (otherwhise won't work)
$tmpAR= New-Object System.Security.AccessControl.FileSystemAuditRule(
'Everyone',
[System.Security.AccessControl.FileSystemRights]::Delete,
[System.Security.AccessControl.AuditFlags]::Success
)
$Acl.SetAuditRule($tmpAR)
$Acl.SetAuditRuleProtection($false, $false)
# Don't forget the Set-ACL at the end
Set-ACL -Path $Path -AclObject $Acl
In my case that worked.
Note: I used to get the ACL: $Acl = Get-ACL -Path $Path -Audit
Hopping this help.
The trick for me was that you can't do this on a folder or file that does not currently have an SACL (System Access Control List, otherwise known as Audit Rule). I wrote a function that sets the rule on the parent, then recursively iterates through all the children to set the rule, ensuring its being inherited, then removing the extra rule.
<#
.SYNOPSIS
Sets auditing on the file or folder.
.DESCRIPTION
Implements a File System Audit Rule on the file or folder.
.PARAMETER Path (Required)
Specifies the file or folder on which to apply the audit rule.
.PARAMETER Principal (Required)
Specifies the NTAccount name.
.PARAMETER Success (Optional, Required if "Failure" not present)
Specifies to implement an audit rule for successes.
.PARAMETER Failure (Optional, Required if "Success" not present)
Specifies to implement an audit rule for failures.
.PARAMETER Flags (Required)
This is an array of two integers that indicate what to apply the audit rule to and what type of recursion should be used.
Inheritance, Propagation:
This folder only = 0,0
This folder, subfolders and files = 3,0
This folder and subfolders = 1,0
This folder and files = 2,0
Subfolders and files only = 3,2
Subfolders only = 1,2
Files only = 2,3
.EXAMPLE
Set-Auditing
#>
function Set-Auditing {
[CmdletBinding(SupportsShouldProcess=$true)]
Param([Parameter(Mandatory=$true, ValueFromPipeline=$false)] [ValidateNotNullOrEmpty()] [string]$Path,
[Parameter(Mandatory=$true, ValueFromPipeline=$false)] [ValidateNotNullOrEmpty()] [string]$Principal,
[Parameter(Mandatory=$true, ValueFromPipeline=$false)] [ValidateSet("AppendData","ChangePermissions","CreateDirectories","CreateFiles","Delete","DeleteSubdirectoriesAndFiles","ExecuteFile","FullControl","ListDirectory","Modify","Read","ReadAndExecute","ReadAttributes","ReadData","ReadExtendedAttributes","ReadPermissions","Synchronize","TakeOwnership","Traverse","Write","WriteAttributes","WriteData","WriteExtendedAttributes")] [string[]]$Rights,
[Parameter(Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="Both")]
[Parameter(Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="Success")] [switch]$Success,
[Parameter(Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="Both")]
[Parameter(Mandatory=$true, ValueFromPipeline=$false, ParameterSetName="Failure")] [switch]$Failure,
[Parameter(Mandatory=$true, ValueFromPipeline=$false)] [int[]]$Flags)
Begin {
# Determine if audit rule exists
if ($Success.IsPresent) { $AuditFlags=1 } else { $AuditFlags=0 }
if ($Failure.IsPresent) { $AuditFlags+=2 }
# Inheritance Flags
# This folder only = 0
# This folder, subfolders and files = 3
# This folder and subfolders = 1
# This folder and files = 2
# Subfolders and files only = 3
# Subfolders only = 1
# Files only = 2
# Propagation Flags
# This folder only = 0
# This folder, subfolders and files = 0
# This folder and subfolders = 0
# This folder and files = 0
# Subfolders and files only = 2
# Subfolders only = 2
# Files only = 2
# File System Rights
$fsrAppendData =0x000004
$fsrChangePermissions =0x040000
$fsrCreateDirectories =0x000004
$fsrCreateFiles =0x000002
$fsrDelete =0x010000
$fsrDeleteSubdirectoriesAndFiles=0x000040
$fsrExecuteFile =0x000020
$fsrFullControl =0x1F01FF
$fsrListDirectory =0x000001
$fsrModify =0x0301BF
$fsrRead =0x020089
$fsrReadAndExecute =0x0200A9
$fsrReadAttributes =0x000080
$fsrReadData =0x000001
$fsrReadExtendedAttributes =0x000008
$fsrReadPermissions =0x020000
$fsrSynchronize =0x100000
$fsrTakeOwnership =0x080000
$fsrTraverse =0x000020
$fsrWrite =0x000116
$fsrWriteAttributes =0x000100
$fsrWriteData =0x000002
$fsrWriteExtendedAttributes =0x000010
$RightValues=0
for ($i=0; $i -lt $Rights.Count; $i++) {
switch ($Rights[$i]) {
"AppendData" { $RightValues=$RightValues -bor $fsrAppendData }
"ChangePermissions" { $RightValues=$RightValues -bor $fsrChangePermissions }
"CreateDirectories" { $RightValues=$RightValues -bor $fsrCreateDirectories }
"CreateFiles" { $RightValues=$RightValues -bor $fsrCreateFiles }
"Delete" { $RightValues=$RightValues -bor $fsrDelete }
"DeleteSubdirectoriesAndFiles" { $RightValues=$RightValues -bor $fsrDeleteSubdirectoriesAndFiles }
"ExecuteFile" { $RightValues=$RightValues -bor $fsrExecuteFile }
"FullControl" { $RightValues=$RightValues -bor $fsrFullControl }
"ListDirectory" { $RightValues=$RightValues -bor $fsrListDirectory }
"Modify" { $RightValues=$RightValues -bor $fsrModify }
"Read" { $RightValues=$RightValues -bor $fsrRead }
"ReadAndExecute" { $RightValues=$RightValues -bor $fsrReadAndExecute }
"ReadAttributes" { $RightValues=$RightValues -bor $fsrReadAttributes }
"ReadData" { $RightValues=$RightValues -bor $fsrReadData }
"ReadExtendedAttributes" { $RightValues=$RightValues -bor $fsrReadExtendedAttributes }
"ReadPermissions" { $RightValues=$RightValues -bor $fsrReadPermissions }
"Synchronize" { $RightValues=$RightValues -bor $fsrSynchronize }
"TakeOwnership" { $RightValues=$RightValues -bor $fsrTakeOwnership }
"Traverse" { $RightValues=$RightValues -bor $fsrTraverse }
"Write" { $RightValues=$RightValues -bor $fsrWrite }
"WriteAttributes" { $RightValues=$RightValues -bor $fsrWriteAttributes }
"WriteData" { $RightValues=$RightValues -bor $fsrWriteData }
"WriteExtendedAttributes" { $RightValues=$RightValues -bor $fsrWriteExtendedAttributes }
}
}
Write-Verbose "Acquiring object $($FS.FullName)"
$FS=Get-Item -Path $Path
$ACL=Get-Acl -Path $Path -Audit
$NothingToDo=$false
for ($i=0; $i -lt $ACL.Audit.Count; $i++) {
if ($ACL.Audit[$i].IdentityReference.Value -eq $Principal) {
if ($ACL.Audit[$i].AuditFlags.value__ -eq $AuditFlags) {
if ($ACL.Audit[$i].PropagationFlags.value__ -eq $Flags[1]) {
if ($ACL.Audit[$i].InheritanceFlags.value__ -eq $Flags[0]) {
if ($ACL.Audit[$i].FileSystemRights.value__ -eq $RightValues) { $NothingToDo=$true; Write-Verbose "Nothing to do" }
}
}
}
}
}
}
Process {
if (!$NothingToDo) {
# There is one case where we will not propagage the rules. This is when $Flags = 0,0
if (($Flags[0] -eq 0) -and ($Flags[1] -eq 0)) { Write-Verbose "Flags = 0,0; no propagation necessary." }
else {
Write-Verbose "Setting Audit Rule"
if ($Principal.Contains("\")) { $NTAccount=New-Object System.Security.Principal.NTAccount(($Principal.Split("\"))[0],($Principal.Split("\"))[1]) }
else { $NTAccount=New-Object System.Security.Principal.NTAccount($Principal) }
$FSAR=New-Object System.Security.AccessControl.FileSystemAuditRule($NTAccount,$RightValues,$Flags[0],$Flags[1],$AuditFlags)
$FAR=New-Object System.Security.AccessControl.FileSystemAuditRule($NTAccount,$RightValues,$AuditFlags)
$ACL.AddAuditRule($FSAR)
$ACL.SetAuditRuleProtection($false, $true)
Write-Verbose "Applying rule to $($ACL.Path.Replace('Microsoft.PowerShell.Core\FileSystem::',''))"
$FS.SetAccessControl($ACL)
# Now, ensure that all folders and files have inheritance enabled.
$FS=Get-ChildItem -Path $Path -Recurse
$FS=#($FS)
for ($i=0; $i -lt $FS.Count; $i++) {
Write-Verbose "Acquiring object $($FS[$i].FullName)"
$ACL=Get-Acl -Path $FS[$i].FullName -Audit
if (Test-Path $ACL.Path -PathType Leaf) { $ACL.AddAuditRule($FAR) } else { $ACL.AddAuditRule($FSAR) }
$ACL.SetAuditRuleProtection($false, $true)
$FS[$i].SetAccessControl($ACL)
Write-Verbose "Applying rule to $($ACL.Path.Replace('Microsoft.PowerShell.Core\FileSystem::',''))"
if (Test-Path $ACL.Path -PathType Leaf) { $ACL.RemoveAuditRule($FAR) > $null } else { $ACL.RemoveAuditRule($FSAR) > $null }
Write-Verbose "Removing extra rule from $($ACL.Path)"
$FS[$i].SetAccessControl($ACL)
}
}
}
else { Write-Verbose "Nothing to do." }
}
}
I'm looking to get all of the namespaces in a WinMD file programmatically. I would prefer a PowerShell or C#-based solution since I need it to be in a script, but any language will do as long as it gets the job done.
Here is the code I have right now, using Assembly.ReflectionOnlyLoadFrom:
var domain = AppDomain.CurrentDomain;
ResolveEventHandler assemblyHandler = (o, e) => Assembly.ReflectionOnlyLoad(e.Name);
EventHandler<NamespaceResolveEventArgs> namespaceHandler = (o, e) =>
{
string file = WindowsRuntimeMetadata
.ResolveNamespace(e.NamespaceName, Array.Empty<string>())
.FirstOrDefault();
if (file == null)
return;
var assembly = Assembly.ReflectionOnlyLoadFrom(file);
e.ResolvedAssemblies.Add(assembly);
};
try
{
// Load it! (plain .NET assemblies)
return Assembly.LoadFrom(path);
}
catch
{
try
{
// Hook up the handlers
domain.ReflectionOnlyAssemblyResolve += assemblyHandler;
WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve += namespaceHandler;
// Load it again! (WinMD components)
return Assembly.ReflectionOnlyLoadFrom(path);
}
finally
{
// Detach the handlers
domain.ReflectionOnlyAssemblyResolve -= assemblyHandler;
WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve -= namespaceHandler;
}
}
For some reason, it doesn't seem to be working. When I run it, I'm getting a ReflectionTypeLoadException when I try to load WinMD files. (You can see this question for the full details.)
So my question is, what's the best way to go about doing this, if the Reflection APIs aren't working? How do tools like Visual Studio or ILSpy do this when you hit F12 on a WinRT type? Is there any way to do this from PowerShell?
TL;DR: How do I extract all of the namespaces from a WinMD file? Any language solution accepted.
Thanks.
Ended up taking up #PetSerAl's suggestion for using Mono.Cecil, which is actually pretty solid. Here's the approach I ended up taking (written in PowerShell):
# Where the real work happens
function Get-Namespaces($assembly)
{
Add-CecilReference
$moduleDefinition = [Mono.Cecil.ModuleDefinition]
$module = $moduleDefinition::ReadModule($assembly)
return $module.Types | ? IsPublic | % Namespace | select -Unique
}
function Extract-Nupkg($nupkg, $out)
{
Add-Type -AssemblyName 'System.IO.Compression.FileSystem' # PowerShell lacks native support for zip
$zipFile = [IO.Compression.ZipFile]
$zipFile::ExtractToDirectory($nupkg, $out)
}
function Add-CecilReference
{
$url = 'https://www.nuget.org/api/v2/package/Mono.Cecil'
$directory = $PSScriptRoot, 'bin', 'Mono.Cecil' -Join '\'
$nupkg = Join-Path $directory 'Mono.Cecil.nupkg'
$assemblyPath = $directory, 'lib', 'net45', 'Mono.Cecil.dll' -Join '\'
if (Test-Path $assemblyPath)
{
# Already downloaded it from a previous script run/function call
Add-Type -Path $assemblyPath
return
}
ri -Recurse -Force $directory 2>&1 | Out-Null
mkdir -f $directory | Out-Null # prevent this from being interpreted as a return value
iwr $url -OutFile $nupkg
Extract-Nupkg $nupkg -Out $directory
Add-Type -Path $assemblyPath
}
You can find the full contents of the script here.
after Microsoft marked the BuildEngine.Engine and BuildEngine.Project as obsolete i have tried to use the new proposal from Microsoft how you can see it underneath. But i have no idea where i can integrate the xmlprojectfile. Is here someone who knows the solution of this problem?
The XML project file content
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ProjectToBuild Include ="myproject.csproj" />
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(ProjectToBuild)"
Properties="Configuration=Debug" StopOnFirstFailure="true" />
</Target>
</Project>
The old and working version (but obsolete)
Microsoft.Build.BuildEngine.Engine engine = new Microsoft.Build.BuildEngine.Engine();
engine.DefaultToolsVersion = "4.0";
engine.RegisterLogger(new ConsoleLogger());
Microsoft.Build.BuildEngine.Project project = new Microsoft.Build.BuildEngine.Project(engine);
project.Load(xmlprojectfile);
if (!project.Build())
{
Console.ReadLine();
}
The new not working verison
Microsoft.Build.Evaluation.ProjectCollection collection = new Microsoft.Build.Evaluation.ProjectCollection();
collection.DefaultToolsVersion = "4.0";
collection.RegisterLogger(new ConsoleLogger());
Microsoft.Build.Evaluation.Project project = new Microsoft.Build.Evaluation.Project(collection);
if (!project.Build())
{
Console.ReadLine();
}
You also need to create a Microsoft.Build.Execution.BuildRequestData object containing the XML project file and a dictionary of std properties like Configuration and Platform. Then you create a Microsoft.Build.Execution.BuildParameters object containing your ProjectCollection object from your code snippet and pass that off to the default Microsoft.Build.Execution.BuildManager.
powershell pseudo code:
#set build properties
$props = new-object "System.Collections.Generic.Dictionary``2[[System.String],[System.String]]"
$props.Add("Configuration",$config)
$props.Add("Platform",$platform)
#create the projectCollection
$projectCollection = new-object Microsoft.Build.Evaluation.ProjectCollection -argumentList $props,$loggers,"ConfigurationFile,Registry"
$request = new-object Microsoft.Build.Execution.BuildRequestData -argumentlist $project,$props,$null,$targets,$null
#create a BuildParameters object to hold the Project Collection
$parameters = new-object Microsoft.Build.Execution.BuildParameters -argumentlist #($projectCollection)
$parameters.MaxNodeCount = 1
$parameters.Loggers = $projectCollection.Loggers
$parameters.ToolsetDefinitionLocations = "ConfigurationFile,Registry"
$parameters.DefaultToolsVersion = $toolsVersion
#get the build manager and submit a build request with the appropriate parameters
$manager = [Microsoft.Build.Execution.BuildManager]::DefaultBuildManager
$result = $manager.Build($parameters, $request)
You are missing project.Load(xmlprojectfile); line in your migrated code. You have to add xmlprojectfile into projectcollection somehow, I guess.
Instead of creating a new Project with new keyword:
// var project = new Project(collection);
Use this:
var project = collection.LoadProject("myproject.csproj")