diff --git a/.travis.yml b/.travis.yml
index 698b30efa..70c605a7c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,6 +7,10 @@ matrix:
- os: osx
osx_image: xcode8
language: generic
+cache:
+ directories:
+ - $HOME/.dotnet
+ - $HOME/.nuget
addons:
artifacts:
s3_region: "us-west-2"
diff --git a/appveyor.yml b/appveyor.yml
index f743073eb..477221cf7 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,4 +1,7 @@
-version: 0.5.0.{build}
+version: 0.6.0.{build}
+
+cache:
+ - '%LocalAppData%\Microsoft\dotnet'
nuget:
project_feed: true
diff --git a/build.psm1 b/build.psm1
index e868767f3..1599cce29 100644
--- a/build.psm1
+++ b/build.psm1
@@ -201,9 +201,9 @@ function Start-PSBuild {
try {
Push-Location $Native
- cmake -DCMAKE_BUILD_TYPE=Debug .
- make -j
- make test
+ Start-NativeExecution { cmake -DCMAKE_BUILD_TYPE=Debug . }
+ Start-NativeExecution { make -j }
+ Start-NativeExecution { ctest --verbose }
} finally {
Pop-Location
}
@@ -556,7 +556,10 @@ Built upon .NET Core, it is also a C# REPL.
Write-Verbose "Packaging $Source"
if ($IsWindows) {
- $msiPackagePath = New-MSIPackage -ProductSourcePath $Source -ProductVersion $Version -AssetsPath "$PSScriptRoot\Assets"
+
+ # Product Guid needs to be unique for every PowerShell version to allow SxS install
+ $productGuid = [guid]::NewGuid()
+ $msiPackagePath = New-MSIPackage -ProductSourcePath $Source -ProductVersion $Version -AssetsPath "$PSScriptRoot\Assets" -ProductGuid $productGuid
$appxPackagePath = New-AppxPackage -PackageVersion $Version -SourcePath $Source -AssetsPath "$PSScriptRoot\Assets"
$packages = @($msiPackagePath, $appxPackagePath)
diff --git a/demos/1-PowerShellGet.ps1 b/demos/1-PowerShellGet.ps1
index 8a3a70495..769fa6f44 100644
--- a/demos/1-PowerShellGet.ps1
+++ b/demos/1-PowerShellGet.ps1
@@ -4,11 +4,13 @@
Get-PackageProvider #(should show 2 providers - NuGet,PowerShellGet)
## Using PowerShellGet find and install other demos
+# Value: equivalent of pypi
+# Look for all the modules we'll be demoing today
+Find-Module -Tag 'Open PowerShell','Demos'
+# Pipe this to Install-Module to install them
Find-Module -Tag 'Open PowerShell','Demos' | Install-Module -Verbose
Get-Module
-
-# Find and Download node.js from nuget.org
-Find-Package -Name node.js -ProviderName NuGet -Verbose -Source http://nuget.org/api/v2
+# Note that ScriptAnalyzer gets installed because the VSCode demo is dependent upon it
# Register trusted endpoints
Register-PackageSource -Name NuGet -Location http://nuget.org/api/v2 -Trusted -ProviderName NuGet
diff --git a/demos/apache.ps1 b/demos/apache.ps1
new file mode 100644
index 000000000..e9f3cd2bc
--- /dev/null
+++ b/demos/apache.ps1
@@ -0,0 +1,14 @@
+# Get-ApacheVHosts
+# Enumerate configured virtual hosts (web sites) on an apache server
+
+# New-ApacheVHost
+# Given defined parameters, create a $ServerName.conf virtual host config file at $VHostsDirectory
+
+# Get-ApacheModules
+# List installed Apache Modules
+
+# Demo execution:
+# Run Get-ApacheVHosts and enumerate configured virtual hosts
+# Run Get-ApacheModules to show installed Apache modules
+# Run New-ApacheVHost to create a new virtual host and restart Apache
+# Run Get-ApacheVhosts to show the newly created virtual host
diff --git a/demos/crontab.ps1 b/demos/crontab.ps1
new file mode 100644
index 000000000..d90e2dbdb
--- /dev/null
+++ b/demos/crontab.ps1
@@ -0,0 +1,9 @@
+# Use the crontab module available at ./modules/CronTab
+
+# Get the existing cron jobs
+
+# Sort them by data
+
+# Create a new one using a DateTime object
+
+# Show in bash that the new cron job exists
diff --git a/demos/dsc.ps1 b/demos/dsc.ps1
new file mode 100644
index 000000000..c1fdd6903
--- /dev/null
+++ b/demos/dsc.ps1
@@ -0,0 +1,11 @@
+# DSC MOF Compilation
+# DSC Configuration() script that:
+# Defines base configuration users, groups, settings
+# Uses PS function to set package configuration (ensure=Present) for an array of packages
+# Probes for the existence of a package (Apache or MySQL) and conditionally configures the workload. I.e., if Apache is installed, configure Apache settings
+
+# Demo execution:
+# Show the .ps1
+# Run the .ps1 to generate a MOF
+# Apply the MOF locally with Start-DSCConfiguration
+# Show the newly configured state
\ No newline at end of file
diff --git a/demos/json.ps1 b/demos/json.ps1
new file mode 100644
index 000000000..4804ffc69
--- /dev/null
+++ b/demos/json.ps1
@@ -0,0 +1,11 @@
+# Get-Mongostat
+# Get mongodb statistics with parsed JSON
+
+# journalctl is a native tool to emit SystemD's journal, which includes diagnostic info pertaining to SystemD services
+# Show-JournalCtl
+# Given journalctl parameters, run journalctl with a -o json to emit JSON, e.g. journalctl $Parameters -o json --no-pager
+# Return structured data from Journal
+
+# Demo execution
+# Show journatcl example
+# Using Where & Sort, filter journal output to drill into a specific problem (e.g. service failed to start)
\ No newline at end of file
diff --git a/demos/modules/ApacheHTTPServer/ApacheVirtualHosts.psm1 b/demos/modules/ApacheHTTPServer/ApacheVirtualHosts.psm1
new file mode 100644
index 000000000..94710c813
--- /dev/null
+++ b/demos/modules/ApacheHTTPServer/ApacheVirtualHosts.psm1
@@ -0,0 +1,173 @@
+$moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path
+
+
+Function New-ApacheVHost {
+ [CmdletBinding()]
+ param (
+ [parameter (Mandatory = $true)][string]$ServerName,
+ [string]$VHostsDirectory,
+ [parameter (Mandatory = $true)][string]$DocumentRoot,
+ [parameter (Mandatory = $true)][string]$VirtualHostIPAddress,
+ [string[]]$ServerAliases,
+ [parameter (Mandatory = $true)][string]$VirtualHostPort,
+ [string]$ServerAdmin,
+ [string]$CustomLogPath,
+ [string]$ErrorLogPath
+ )
+
+ if ($VHostsDirectory -notlike "*/*"){
+ #Try to find default
+ if (Test-Path "/etc/httpd/conf.d"){
+ $VHostsDirectory = "/etc/httpd/conf.d/"
+ }
+ if (Test-Path "/etc/apache2/sites-enabled"){
+ $VHostsDirectory = "/etc/apache2/sites-enabled"
+ }
+ }
+
+ if (!(Test-Path $VHostsDirectory)){
+ Write-Error "Specified virtual hosts directory does not exist: $VHostsDirectory"
+ exit 1
+ }
+
+ $vHostDef = ""
+
+ $vHostDef += "`n"
+ $vHostDef += "DocumentRoot " + $DocumentRoot + "`n"
+ $vHostDef += "ServerName " + $ServerName + "`n"
+ if ($ServerAliases -ne $null){
+ ForEach ($Alias in $ServerAliases){
+ $vHostDef += "ServerAlias " + $Alias + "`n"
+ }
+ }
+ if ($ServerAdmin.Length -gt 1){
+ $vHostDef += "ServerAdmin " + $ServerAdmin +"`n"
+ }
+ if ($CustomLogPath -like "*/*"){
+ $vHostDef += "CustomLog " + $CustomLogPath +"`n"
+ }
+ if ($ErrorLogPath -like "*/*"){
+ $vHostDef += "ErrorLog " + $ErrorLogpath +"`n"
+ }
+ $vHostDef += ""
+ $filName = $ServerName + ".conf"
+ Write-Output "Writing $filName to $VHostsDirectory"
+ $VhostDef |out-file "$VHostsDirectory/$filName" -Force -Encoding:ascii
+
+ if (Test-Path "/usr/sbin/apache2ctl"){
+ $cmd = "/usr/sbin/apache2ctl"
+ }elseif(Test-Path "/usr/sbin/httpd"){
+ $cmd = "/usr/sbin/httpd"
+ }
+ $cmd += " -k restart"
+ write-output "Restarting Apache HTTP daemon"
+ Invoke-expression $cmd
+}
+
+Function Get-ApacheVHosts{
+ $cmd = $null
+ if (Test-Path "/usr/sbin/apache2ctl"){
+ $cmd = "/usr/sbin/apache2ctl"
+ }elseif(Test-Path "/usr/sbin/httpd"){
+ $cmd = "/usr/sbin/httpd"
+ }
+
+
+ function New-VHostObj {
+ New-Object PSObject -Property @{}
+ }
+
+
+
+ if ($cmd -eq $null){
+ Write-Error "Unable to find httpd/apache2ctl command"
+ exit -1
+ }
+
+ $cmd = $cmd += " -t -D DUMP_VHOSTS"
+ $Vhosts = @()
+ $res = Invoke-expression $cmd
+ ForEach ($line in $res){
+ if ($line -like "*.conf*"){
+ $vhobject = New-VHostObj
+ $RMatch = $line -match "(?.*:[0-9]*)(?.*)\((?.*)\)"
+ $VHostProps = $matches
+ $vhobject |Add-Member -type NoteProperty -name "ServerName" -value $matches.ServerName.Trim()
+ $vhobject |Add-Member -type NoteProperty -name "ListenAddress" -value $matches.Listen.Trim()
+ $vhobject |Add-Member -type NoteProperty -name "ConfFile" -value $matches.ConfFile.Trim()
+ $Vhosts += $vhobject
+ }
+
+ }
+
+ Return $Vhosts
+
+}
+
+Function Restart-ApacheHTTPServer{
+ [CmdletBinding()]
+ Param(
+ [boolean]$Graceful
+ )
+
+ if ($Graceful -eq $null){$Graceful = $fase}
+
+ $cmd = $null
+ if (Test-Path "/usr/sbin/apache2ctl"){
+ $cmd = "/usr/sbin/apache2ctl"
+ }elseif(Test-Path "/usr/sbin/httpd"){
+ $cmd = "/usr/sbin/httpd"
+ }
+
+ if ($cmd -eq $null){
+ Write-Error "Unable to find httpd/apache2ctl command"
+ exit -1
+ }
+
+ if ($Graceful){
+ $cmd = $cmd += " -k graceful"
+ }else{
+ $cmd = $cmd += " -k restart"
+ }
+
+ Invoke-Expression $Cmd
+}
+
+Function Get-ApacheModules{
+ $cmd = $null
+ if (Test-Path "/usr/sbin/apache2ctl"){
+ $cmd = "/usr/sbin/apache2ctl"
+ }elseif(Test-Path "/usr/sbin/httpd"){
+ $cmd = "/usr/sbin/httpd"
+ }
+
+ if ($cmd -eq $null){
+ Write-Error "Unable to find httpd/apache2ctl command"
+ exit -1
+ }
+
+ $cmd = $cmd += " -M |grep -v Loaded"
+
+ $ApacheModules = @()
+ function New-ModuleObj {
+ New-Object PSObject -Property @{}
+ }
+
+
+ $Results = Invoke-Expression $cmd
+ Foreach ($mod in $Results){
+ $modObj = New-ModuleObj
+ $modObj | Add-Member -type NoteProperty -name "Module" -value $mod.trim()
+ $ApacheModules += $modObj
+ }
+
+ Return $ApacheModules
+
+}
diff --git a/demos/modules/CronTab/CronTab.ps1xml b/demos/modules/CronTab/CronTab.ps1xml
new file mode 100644
index 000000000..1d1f1f90d
--- /dev/null
+++ b/demos/modules/CronTab/CronTab.ps1xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+ Default
+
+ CronJob
+
+
+
+
+
+ 10
+ Left
+
+
+
+ 10
+ Left
+
+
+
+ 10
+ Left
+
+
+
+ 10
+ Left
+
+
+
+ 10
+ Left
+
+
+
+ Left
+
+
+
+
+
+
+ Minute
+
+
+ Hour
+
+
+ DayOfMonth
+
+
+ Month
+
+
+ DayOfWeek
+
+
+ Command
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demos/modules/CronTab/CronTab.psd1 b/demos/modules/CronTab/CronTab.psd1
new file mode 100644
index 000000000..23d9d95f0
Binary files /dev/null and b/demos/modules/CronTab/CronTab.psd1 differ
diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/modules/CronTab/CronTab.psm1
new file mode 100644
index 000000000..f8bd2e7c4
--- /dev/null
+++ b/demos/modules/CronTab/CronTab.psm1
@@ -0,0 +1,189 @@
+$crontabcmd = "/usr/bin/crontab"
+
+#TODO fix after https://github.com/PowerShell/PowerShell/issues/932 is fixed
+#class CronJob {
+# [string] $Minute
+# [string] $Hour
+# [string] $DayOfMonth
+# [string] $Month
+# [string] $DayOfWeek
+# [string] $Command
+#}
+
+# Internal helper functions
+
+function Get-CronTab ([String] $user) {
+ $crontab = Invoke-CronTab -user $user -arguments "-l" -noThrow
+ if ($crontab -is [System.Management.Automation.ErrorRecord]) {
+ if ($crontab.Exception.Message.StartsWith("no crontab for ")) {
+ $crontab = $null
+ }
+ else {
+ throw $crontab.Exception
+ }
+ }
+ [string[]] $crontab
+}
+
+function ConvertTo-CronJob ([String] $crontab) {
+ $split = $crontab.split(" ", 6)
+ $cronjob = New-Object -TypeName PSObject -Property @{ #TODO: change to CronJob type
+ PSTypeName="CronJob";
+ Minute = $split[0];
+ Hour = $split[1];
+ DayOfMonth= $split[2];
+ Month =$split[3];
+ DayOfWeek = $split[4];
+ Command = $split[5]
+ }
+ $cronjob
+}
+
+function Invoke-CronTab ([String] $user, [String] $arguments, [Switch] $noThrow) {
+ If ($user -ne [String]::Empty) {
+ $arguments = "-u $UserName $arguments"
+ }
+
+ $cmd = "$crontabcmd $arguments 2>&1"
+ Write-Verbose $cmd
+ $output = Invoke-Expression $cmd
+ if ($LastExitCode -ne 0 -and -not $noThrow) {
+ $e = New-Object System.InvalidOperationException -ArgumentList $output.Exception.Message
+ throw $e
+ } else {
+ $output
+ }
+}
+
+function Import-CronTab ([String] $user, [String[]] $crontab) {
+ $temp = New-TemporaryFile
+ [String]::Join([Environment]::NewLine,$crontab) | Set-Content $temp.FullName
+ Invoke-CronTab -user $user $temp.FullName
+ Remove-Item $temp
+}
+
+# Public functions
+
+function Remove-CronJob {
+<#
+.SYNOPSIS
+ Removes the exactly matching cron job from the cron table
+.DESCRIPTION
+ Removes the exactly matching cron job from the cron table
+.EXAMPLE
+ Get-CronJob | ? {%_.Command -like 'foo *'} | Remove-CronJob
+.RETURNVALUE
+ None
+.PARAMETER UserName
+ Optional parameter to specify a specific user's cron table
+.PARAMETER Job
+ Cron job object returned from Get-CronJob
+#>
+ [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
+ param (
+ [Alias("u")][Parameter(Mandatory=$false)][String] $UserName,
+ [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][PSTypeName("CronJob")] $Job #TODO use CronJob type
+ )
+ process {
+
+ [string[]] $crontab = Get-CronTab -user $UserName
+ [string[]] $newcrontab = $null
+ $found = $false
+
+ foreach ($line in $crontab) {
+ $cronjob = ConvertTo-CronJob -crontab $line
+ if ((Compare-object $cronjob.psobject.properties $Job.psobject.properties) -eq $null) {
+ $found = $true
+ } else {
+ $newcrontab += $line
+ }
+ }
+
+ if (-not $found) {
+ $e = New-Object System.Exception -ArgumentList "Job not found"
+ throw $e
+ }
+ if ($pscmdlet.ShouldProcess($Job.Command,"Remove")) {
+ Import-CronTab -user $UserName -crontab $newcrontab
+ }
+ }
+}
+
+function New-CronJob {
+<#
+.SYNOPSIS
+ Create a new cron job
+.DESCRIPTION
+ Create a new job in the cron table. Date and time parameters can be specified
+ as ranges such as 10-30, as a list: 5,6,7, or combined 1-5,10-15. An asterisk
+ means 'first through last' (the entire allowed range). Step values can be used
+ with ranges or with an asterisk. Every 2 hours can be specified as either
+ 0-23/2 or */2.
+.EXAMPLE
+ New-CronJob -Minute 10-30 -Hour 10-20/2 -DayOfMonth */2 -Command "/bin/bash -c 'echo hello' > ~/hello"
+.RETURNVALUE
+ If successful, an object representing the cron job is returned
+.PARAMETER UserName
+ Optional parameter to specify a specific user's cron table
+.PARAMETER Minute
+ Valid values are 0 to 59. If not specified, defaults to *.
+.PARAMETER Hour
+ Valid values are 0-23. If not specified, defaults to *.
+.PARAMETER DayOfMonth
+ Valid values are 1-31. If not specified, defaults to *.
+.PARAMETER Month
+ Valid values are 1-12. If not specified, defaults to *.
+.PARAMETER DayOfWeek
+ Valid values are 0-7. 0 and 7 are both Sunday. If not specified, defaults to *.
+.PARAMETER Command
+ Command to execute at the scheduled time and day.
+#>
+ [CmdletBinding()]
+ param (
+ [Alias("u")][Parameter(Mandatory=$false)][String] $UserName,
+ [Alias("mi")][Parameter(Position=1)][String[]] $Minute = "*",
+ [Alias("h")][Parameter(Position=2)][String[]] $Hour = "*",
+ [Alias("dm")][Parameter(Position=3)][String[]] $DayOfMonth = "*",
+ [Alias("mo")][Parameter(Position=4)][String[]] $Month = "*",
+ [Alias("dw")][Parameter(Position=5)][String[]] $DayOfWeek = "*",
+ [Alias("c")][Parameter(Mandatory=$true,Position=6)][String] $Command
+ )
+ process {
+ # TODO: validate parameters, note that different versions of crontab support different capabilities
+ $line = "{0} {1} {2} {3} {4} {5}" -f [String]::Join(",",$Minute), [String]::Join(",",$Hour),
+ [String]::Join(",",$DayOfMonth), [String]::Join(",",$Month), [String]::Join(",",$DayOfWeek), $Command
+ [string[]] $crontab = Get-CronTab -user $UserName
+ $crontab += $line
+ Import-CronTab -User $UserName -crontab $crontab
+ ConvertTo-CronJob -crontab $line
+ }
+}
+
+function Get-CronJob {
+<#
+.SYNOPSIS
+ Returns the current cron jobs from the cron table
+.DESCRIPTION
+ Returns the current cron jobs from the cron table
+.EXAMPLE
+ Get-CronJob -UserName Steve
+.RETURNVALUE
+ CronJob objects
+.PARAMETER UserName
+ Optional parameter to specify a specific user's cron table
+#>
+ [CmdletBinding()]
+ [OutputType([PSObject])]
+ param (
+ [Alias("u")][Parameter(Mandatory=$false)][String] $UserName
+ )
+ process {
+ $crontab = Get-CronTab -user $UserName
+ ForEach ($line in $crontab) {
+ if ($line.Trim().Length -gt 0)
+ {
+ ConvertTo-CronJob -crontab $line
+ }
+ }
+ }
+}
diff --git a/demos/modules/README.md b/demos/modules/README.md
new file mode 100644
index 000000000..9a7f330f3
--- /dev/null
+++ b/demos/modules/README.md
@@ -0,0 +1 @@
+# Samples
diff --git a/demos/remoting.ps1 b/demos/remoting.ps1
new file mode 100644
index 000000000..fe909db1b
--- /dev/null
+++ b/demos/remoting.ps1
@@ -0,0 +1,2 @@
+# Show PSRP working over OpenSSH
+# Includes Windows -> Linux and Linux -> Windows
\ No newline at end of file
diff --git a/demos/rest.ps1 b/demos/rest.ps1
new file mode 100644
index 000000000..a7b8c799f
--- /dev/null
+++ b/demos/rest.ps1
@@ -0,0 +1,7 @@
+# Get all repositories from the GitHub API, sort by most pull requests, most stars, most forks, etc.
+
+# Get repo info (json) from the github api
+# Convert json to PS object
+# Set the private status to false
+# Convert back to json
+# Push the new json back to the github api
\ No newline at end of file
diff --git a/demos/xml.ps1 b/demos/xml.ps1
new file mode 100644
index 000000000..151ee628f
--- /dev/null
+++ b/demos/xml.ps1
@@ -0,0 +1,42 @@
+Dev: XML legacy [ETS?]
+What are some xml workloads we can simplify with XML parsing?
+Maybe cracking open some repository metadata?
+JEE servers use XML pretty extensively for configuration. Perhaps a script to adjust JEE server settings? Obviously, parsing XML in bash is fraught with danger...
+
+PS /etc/tomcat> $tomcatserverfile="/usr/share/tomcat/conf/server.xml"
+PS /etc/tomcat> [xml]$tomcatconfig=Get-Content $tomcatserverfile
+PS /etc/tomcat> $tomcatconfig.Server
+
+
+port : 8005
+shutdown : SHUTDOWN
+#comment : { Security listener. Documentation at /docs/config/listeners.html
+
+ , APR library loader. Documentation at /docs/apr.html , Initialize Jasper prior to webapps are loaded. Documentation at
+ /docs/jasper-howto.html , Prevent memory leaks due to use of particular java/javax APIs...}
+Listener : {Listener, Listener, Listener, Listener...}
+GlobalNamingResources : GlobalNamingResources
+Service : Service
+
+
+
+
+PS /etc/tomcat> $tomcatconfig.Server.port
+8005
+PS /etc/tomcat> $tomcatconfig.Server.Listener
+
+className SSLEngine
+--------- ---------
+org.apache.catalina.core.AprLifecycleListener on
+org.apache.catalina.core.JasperListener
+org.apache.catalina.core.JreMemoryLeakPreventionListener
+org.apache.catalina.mbeans.GlobalResourcesLifecycleListener
+org.apache.catalina.core.ThreadLocalLeakPreventionListener
+
+
+PS /etc/tomcat> $tomcatconfig.Server.Service
+
+name #comment
+---- --------
+Catalina {The connectors can use a shared executor, you can define one or more named thread pools, ...
+
diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
index 073c1e5fe..1a622ed28 100644
--- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
+++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs
@@ -866,6 +866,9 @@ namespace Microsoft.PowerShell.Commands
private static string RetrieveProcessUserName(Process process, Cmdlet cmdlet)
{
string userName = null;
+#if LINUX
+ userName = Platform.NonWindowsGetUserFromPid(process.Id);
+#else
IntPtr tokenUserInfo = IntPtr.Zero;
IntPtr processTokenHandler = IntPtr.Zero;
@@ -955,6 +958,7 @@ namespace Microsoft.PowerShell.Commands
}
}
+#endif
return userName;
}
diff --git a/src/Microsoft.PowerShell.ScheduledJob/AssemblyInfo.cs b/src/Microsoft.PowerShell.ScheduledJob/AssemblyInfo.cs
new file mode 100644
index 000000000..8be322935
--- /dev/null
+++ b/src/Microsoft.PowerShell.ScheduledJob/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+using System.Reflection;
+using System.Resources;
+
+[assembly:AssemblyFileVersionAttribute("3.0.0.0")]
+[assembly:AssemblyVersion("3.0.0.0")]
+
+[assembly:AssemblyCulture("")]
+[assembly:NeutralResourcesLanguage("en-US")]
diff --git a/src/Modules/Shared/PowerShellGet/PSGet.Resource.psd1 b/src/Modules/Shared/PowerShellGet/PSGet.Resource.psd1
index 6dfd08222..5fc17b600 100644
Binary files a/src/Modules/Shared/PowerShellGet/PSGet.Resource.psd1 and b/src/Modules/Shared/PowerShellGet/PSGet.Resource.psd1 differ
diff --git a/src/Modules/Shared/PowerShellGet/PSModule.psm1 b/src/Modules/Shared/PowerShellGet/PSModule.psm1
index 6f53550ef..5d1804eb6 100644
--- a/src/Modules/Shared/PowerShellGet/PSModule.psm1
+++ b/src/Modules/Shared/PowerShellGet/PSModule.psm1
@@ -14,25 +14,43 @@ Microsoft.PowerShell.Core\Set-StrictMode -Version Latest
# Check if this is nano server. [System.Runtime.Loader.AssemblyLoadContext] is only available on NanoServer
$script:isNanoServer = $null -ne ('System.Runtime.Loader.AssemblyLoadContext' -as [Type])
-try
+function IsWindows { $PSVariable = Get-Variable -Name IsWindows -ErrorAction Ignore; return (-not $PSVariable -or $PSVariable.Value) }
+function IsLinux { $PSVariable = Get-Variable -Name IsLinux -ErrorAction Ignore; return ($PSVariable -and $PSVariable.Value) }
+function IsCore { $PSVariable = Get-Variable -Name IsCore -ErrorAction Ignore; return ($PSVariable -and $PSVariable.Value) }
+
+if(IsWindows)
{
- $script:MyDocumentsFolderPath = [Environment]::GetFolderPath("MyDocuments")
+ $script:ProgramFilesPSPath = Microsoft.PowerShell.Management\Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell"
}
-catch
+else
{
- $script:MyDocumentsFolderPath = $null
+ $script:ProgramFilesPSPath = $PSHome
}
-$script:ProgramFilesPSPath = Microsoft.PowerShell.Management\Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell"
+if(IsWindows)
+{
+ try
+ {
+ $script:MyDocumentsFolderPath = [Environment]::GetFolderPath("MyDocuments")
+ }
+ catch
+ {
+ $script:MyDocumentsFolderPath = $null
+ }
-$script:MyDocumentsPSPath = if($script:MyDocumentsFolderPath)
- {
- Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsFolderPath -ChildPath "WindowsPowerShell"
- }
- else
- {
- Microsoft.PowerShell.Management\Join-Path -Path $env:USERPROFILE -ChildPath "Documents\WindowsPowerShell"
- }
+ $script:MyDocumentsPSPath = if($script:MyDocumentsFolderPath)
+ {
+ Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsFolderPath -ChildPath "WindowsPowerShell"
+ }
+ else
+ {
+ Microsoft.PowerShell.Management\Join-Path -Path $env:USERPROFILE -ChildPath "Documents\WindowsPowerShell"
+ }
+}
+else
+{
+ $script:MyDocumentsPSPath = Microsoft.PowerShell.Management\Join-Path -Path $HOME -ChildPath ".local/share/powershell"
+}
$script:ProgramFilesModulesPath = Microsoft.PowerShell.Management\Join-Path -Path $script:ProgramFilesPSPath -ChildPath "Modules"
$script:MyDocumentsModulesPath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsPSPath -ChildPath "Modules"
@@ -41,10 +59,20 @@ $script:ProgramFilesScriptsPath = Microsoft.PowerShell.Management\Join-Path -Pat
$script:MyDocumentsScriptsPath = Microsoft.PowerShell.Management\Join-Path -Path $script:MyDocumentsPSPath -ChildPath "Scripts"
-$script:TempPath = ([System.IO.DirectoryInfo]$env:TEMP).FullName
+$script:TempPath = if(IsWindows){ ([System.IO.DirectoryInfo]$env:TEMP).FullName } else { '/tmp' }
$script:PSGetItemInfoFileName = "PSGetModuleInfo.xml"
-$script:PSGetProgramDataPath = Microsoft.PowerShell.Management\Join-Path -Path $env:ProgramData -ChildPath 'Microsoft\Windows\PowerShell\PowerShellGet\'
-$script:PSGetAppLocalPath = Microsoft.PowerShell.Management\Join-Path -Path $env:LOCALAPPDATA -ChildPath 'Microsoft\Windows\PowerShell\PowerShellGet\'
+
+if(IsWindows)
+{
+ $script:PSGetProgramDataPath = Microsoft.PowerShell.Management\Join-Path -Path $env:ProgramData -ChildPath 'Microsoft\Windows\PowerShell\PowerShellGet\'
+ $script:PSGetAppLocalPath = Microsoft.PowerShell.Management\Join-Path -Path $env:LOCALAPPDATA -ChildPath 'Microsoft\Windows\PowerShell\PowerShellGet\'
+}
+else
+{
+ $script:PSGetProgramDataPath = "$HOME/.config/powershell/powershellget" #TODO: Get $env:ProgramData equivalent
+ $script:PSGetAppLocalPath = "$HOME/.config/powershell/powershellget"
+}
+
$script:PSGetModuleSourcesFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:PSGetAppLocalPath -ChildPath "PSRepositories.xml"
$script:PSGetModuleSources = $null
$script:PSGetInstalledModules = $null
@@ -89,8 +117,8 @@ $script:NuGetProviderVersion = [Version]'2.8.5.201'
$script:SupportsPSModulesFeatureName="supports-powershell-modules"
$script:FastPackRefHastable = @{}
-$script:NuGetBinaryProgramDataPath="$env:ProgramFiles\PackageManagement\ProviderAssemblies"
-$script:NuGetBinaryLocalAppDataPath="$env:LOCALAPPDATA\PackageManagement\ProviderAssemblies"
+$script:NuGetBinaryProgramDataPath=if(IsWindows) {"$env:ProgramFiles\PackageManagement\ProviderAssemblies"}
+$script:NuGetBinaryLocalAppDataPath=if(IsWindows) {"$env:LOCALAPPDATA\PackageManagement\ProviderAssemblies"}
# go fwlink for 'https://nuget.org/nuget.exe'
$script:NuGetClientSourceURL = 'http://go.microsoft.com/fwlink/?LinkID=690216&clcid=0x409'
$script:NuGetExeName = 'NuGet.exe'
@@ -577,7 +605,7 @@ catch
# Ignore the error and try adding the type below
}
-if(-not $script:TelemetryEnabled)
+if(-not $script:TelemetryEnabled -and (IsWindows))
{
try
{
@@ -682,7 +710,7 @@ function Publish-Module
Begin
{
- if($script:isNanoServer) {
+ if($script:isNanoServer -or (IsCore)) {
$message = $LocalizedData.PublishPSArtifactUnsupportedOnNano -f "Module"
ThrowError -ExceptionName "System.InvalidOperationException" `
-ExceptionMessage $message `
@@ -844,7 +872,7 @@ function Publish-Module
}
else
{
- $resolvedPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1
+ $resolvedPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $resolvedPath -or
-not (Microsoft.PowerShell.Management\Test-Path -Path $resolvedPath -PathType Container))
@@ -1034,7 +1062,7 @@ function Publish-Module
# Use Find-Script to check if that name is already used as scriptname
$scriptPSGetItemInfo = Find-Script @FindParameters |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $moduleName} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if($scriptPSGetItemInfo)
{
$message = $LocalizedData.SpecifiedNameIsAlearyUsed -f ($moduleName, $Repository, 'Find-Script')
@@ -1049,7 +1077,7 @@ function Publish-Module
$null = $FindParameters.Remove('Tag')
$currentPSGetItemInfo = Find-Module @FindParameters |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $moduleInfo.Name} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if($currentPSGetItemInfo)
{
@@ -1387,7 +1415,7 @@ function Save-Module
{
if($Path)
{
- $destinationPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1
+ $destinationPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-path $destinationPath))
{
@@ -1404,7 +1432,7 @@ function Save-Module
}
else
{
- $destinationPath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1
+ $destinationPath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $destinationPath))
{
@@ -1808,10 +1836,6 @@ function Update-Module
[PSCredential]
$ProxyCredential,
- [Parameter()]
- [switch]
- $SkipPublisherCheck,
-
[Parameter()]
[Switch]
$Force
@@ -1857,7 +1881,7 @@ function Update-Module
if(-not $installedPackages -and -not (Test-WildcardPattern -Name $moduleName))
{
- $availableModules = Get-Module -ListAvailable $moduleName -Verbose:$false | Microsoft.PowerShell.Utility\Select-Object -Unique
+ $availableModules = Get-Module -ListAvailable $moduleName -Verbose:$false | Microsoft.PowerShell.Utility\Select-Object -Unique -ErrorAction Ignore
if(-not $availableModules)
{
@@ -2485,7 +2509,7 @@ function Publish-Script
Begin
{
- if($script:isNanoServer) {
+ if($script:isNanoServer -or (IsCore)) {
$message = $LocalizedData.PublishPSArtifactUnsupportedOnNano -f "Script"
ThrowError -ExceptionName "System.InvalidOperationException" `
-ExceptionMessage $message `
@@ -2506,7 +2530,7 @@ function Publish-Script
if($Path)
{
$scriptFilePath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $scriptFilePath -or
-not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf))
@@ -2523,7 +2547,7 @@ function Publish-Script
else
{
$scriptFilePath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $scriptFilePath -or
-not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $scriptFilePath -PathType Leaf))
@@ -2677,7 +2701,7 @@ function Publish-Script
# Use Find-Module to check if that name is already used as module name
$modulePSGetItemInfo = Find-Module @FindParameters |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $scriptName} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if($modulePSGetItemInfo)
{
$message = $LocalizedData.SpecifiedNameIsAlearyUsed -f ($scriptName, $Repository, 'Find-Module')
@@ -2694,7 +2718,7 @@ function Publish-Script
$currentPSGetItemInfo = $null
$currentPSGetItemInfo = Find-Script @FindParameters |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $scriptName} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if($currentPSGetItemInfo)
{
@@ -3053,7 +3077,7 @@ function Save-Script
if($Path)
{
$destinationPath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-path $destinationPath))
{
@@ -3071,7 +3095,7 @@ function Save-Script
else
{
$destinationPath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $destinationPath))
{
@@ -4438,7 +4462,7 @@ function Test-ScriptFileInfo
$scriptFilePath = $null
if($Path)
{
- $scriptFilePath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1
+ $scriptFilePath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $scriptFilePath -or -not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf))
{
@@ -4454,7 +4478,7 @@ function Test-ScriptFileInfo
}
else
{
- $scriptFilePath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1
+ $scriptFilePath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $scriptFilePath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $scriptFilePath -PathType Leaf))
{
@@ -4511,7 +4535,7 @@ function Test-ScriptFileInfo
$psscriptInfoComments = $CommentTokens |
Microsoft.PowerShell.Core\Where-Object { $_.Extent.Text -match "<#PSScriptInfo" } |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $psscriptInfoComments)
{
@@ -4700,20 +4724,20 @@ function Test-ScriptFileInfo
if($allCommands)
{
- $allCommandNames = $allCommands | ForEach-Object {$_.Name} | Select-Object -Unique
+ $allCommandNames = $allCommands | ForEach-Object {$_.Name} | Select-Object -Unique -ErrorAction Ignore
ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo `
-PropertyName $script:DefinedCommands `
-PropertyValue $allCommandNames `
-CallerPSCmdlet $PSCmdlet
- $allFunctionNames = $allCommands | Where-Object {-not $_.IsWorkflow} | ForEach-Object {$_.Name} | Select-Object -Unique
+ $allFunctionNames = $allCommands | Where-Object {-not $_.IsWorkflow} | ForEach-Object {$_.Name} | Select-Object -Unique -ErrorAction Ignore
ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo `
-PropertyName $script:DefinedFunctions `
-PropertyValue $allFunctionNames `
-CallerPSCmdlet $PSCmdlet
- $allWorkflowNames = $allCommands | Where-Object {$_.IsWorkflow} | ForEach-Object {$_.Name} | Select-Object -Unique
+ $allWorkflowNames = $allCommands | Where-Object {$_.IsWorkflow} | ForEach-Object {$_.Name} | Select-Object -Unique -ErrorAction Ignore
ValidateAndAdd-PSScriptInfoEntry -PSScriptInfo $PSScriptInfo `
-PropertyName $script:DefinedWorkflows `
-PropertyValue $allWorkflowNames `
@@ -4942,7 +4966,7 @@ function New-ScriptFileInfo
$ScriptMetadataString += $ScriptCommentHelpInfoString
$ScriptMetadataString += "Param()`r`n`r`n"
- $tempScriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $env:TEMP -ChildPath "$(Get-Random).ps1"
+ $tempScriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempPath -ChildPath "$(Get-Random).ps1"
try
{
@@ -5089,7 +5113,7 @@ function Update-ScriptFileInfo
if($Path)
{
$scriptFilePath = Resolve-PathHelper -Path $Path -CallerPSCmdlet $PSCmdlet |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $scriptFilePath -or
-not (Microsoft.PowerShell.Management\Test-Path -Path $scriptFilePath -PathType Leaf))
@@ -5106,7 +5130,7 @@ function Update-ScriptFileInfo
else
{
$scriptFilePath = Resolve-PathHelper -Path $LiteralPath -IsLiteralPath -CallerPSCmdlet $PSCmdlet |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $scriptFilePath -or
-not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $scriptFilePath -PathType Leaf))
@@ -5303,7 +5327,7 @@ function Update-ScriptFileInfo
return
}
- $tempScriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $env:TEMP -ChildPath "$(Get-Random).ps1"
+ $tempScriptFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:TempPath -ChildPath "$(Get-Random).ps1"
try
{
@@ -5359,7 +5383,7 @@ function Update-ScriptFileInfo
$psscriptInfoComments = $CommentTokens |
Microsoft.PowerShell.Core\Where-Object { $_.Extent.Text -match "<#PSScriptInfo" } |
- Microsoft.PowerShell.Utility\Select-Object -First 1
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $psscriptInfoComments)
{
@@ -6129,7 +6153,7 @@ function Ping-Endpoint
$results = @{}
$WebProxy = $null
- if($Proxy)
+ if($Proxy -and (IsWindows))
{
$ProxyNetworkCredential = $null
if($ProxyCredential)
@@ -6359,6 +6383,11 @@ function ValidateAndSet-PATHVariableIfUserAccepts
$Request
)
+ if(-not (IsWindows))
+ {
+ return
+ }
+
Set-PSGetSettingsVariable
# Check and add the scope path to PATH environment variable if USER accepts the prompt.
@@ -7154,9 +7183,10 @@ function Install-NuGetClientBinaries
$Force
)
- if($script:NuGetProvider -and
- (-not $BootstrapNuGetExe -or
- ($script:NuGetExePath -and (Microsoft.PowerShell.Management\Test-Path -Path $script:NuGetExePath))))
+ if(-not (IsWindows) -or
+ ($script:NuGetProvider -and
+ (-not $BootstrapNuGetExe -or
+ ($script:NuGetExePath -and (Microsoft.PowerShell.Management\Test-Path -Path $script:NuGetExePath)))))
{
return
}
@@ -7240,7 +7270,7 @@ function Install-NuGetClientBinaries
$_.Path -and
((Microsoft.PowerShell.Management\Split-Path -Path $_.Path -Leaf) -eq $script:NuGetExeName) -and
(-not $_.Path.StartsWith($env:windir, [System.StringComparison]::OrdinalIgnoreCase))
- } | Microsoft.PowerShell.Utility\Select-Object -First 1
+ } | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if($nugetCmd -and $nugetCmd.Path)
{
@@ -7256,7 +7286,7 @@ function Install-NuGetClientBinaries
}
# On Nano server we don't need NuGet.exe
- if(-not $bootstrapNuGetProvider -and ($script:isNanoServer -or -not $BootstrapNuGetExe))
+ if(-not $bootstrapNuGetProvider -and ($script:isNanoServer -or (IsCore) -or -not $BootstrapNuGetExe))
{
return
}
@@ -7313,7 +7343,7 @@ function Install-NuGetClientBinaries
}
}
- if($BootstrapNuGetExe -and -not $script:isNanoServer)
+ if($BootstrapNuGetExe -and -not $script:isNanoServer -and -not (IsCore))
{
Write-Verbose -Message $LocalizedData.DownloadingNugetExe
@@ -7397,10 +7427,19 @@ function Test-RunningAsElevated
[OutputType([bool])]
Param()
- $wid=[System.Security.Principal.WindowsIdentity]::GetCurrent()
- $prp=new-object System.Security.Principal.WindowsPrincipal($wid)
- $adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator
- return $prp.IsInRole($adm)
+ if(IsWindows)
+ {
+ $wid=[System.Security.Principal.WindowsIdentity]::GetCurrent()
+ $prp=new-object System.Security.Principal.WindowsPrincipal($wid)
+ $adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator
+ return $prp.IsInRole($adm)
+ }
+ elseif(IsLinux)
+ {
+ return ($env:SUDO_UID -eq '1000')
+ }
+
+ return $false
}
function Get-EscapedString
@@ -7486,7 +7525,7 @@ function ValidateAndGet-ScriptDependencies
$psgetItemInfo = Find-Module @FindModuleArguments |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $ModuleName} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if(-not $psgetItemInfo)
{
@@ -7529,7 +7568,7 @@ function ValidateAndGet-ScriptDependencies
$psgetItemInfo = Find-Script @FindScriptArguments |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $requiredScript} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if(-not $psgetItemInfo)
{
@@ -7656,7 +7695,7 @@ function ValidateAndGet-RequiredModuleDetails
$psgetItemInfo = Find-Module @FindModuleArguments |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $ModuleName} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if(-not $psgetItemInfo)
{
@@ -7700,7 +7739,7 @@ function ValidateAndGet-RequiredModuleDetails
$psgetItemInfo = Find-Module @FindModuleArguments |
Microsoft.PowerShell.Core\Where-Object {$_.Name -eq $ModuleName} |
- Microsoft.PowerShell.Utility\Select-Object -Last 1
+ Microsoft.PowerShell.Utility\Select-Object -Last 1 -ErrorAction Ignore
if(-not $psgetItemInfo)
{
@@ -9482,7 +9521,7 @@ function Find-Package
if($options.ContainsKey($script:Tag))
{
- $userSpecifiedTags = $options[$script:Tag] | Microsoft.PowerShell.Utility\Select-Object -Unique
+ $userSpecifiedTags = $options[$script:Tag] | Microsoft.PowerShell.Utility\Select-Object -Unique -ErrorAction Ignore
}
else
{
@@ -9493,7 +9532,7 @@ function Find-Package
if($options.ContainsKey('DscResource'))
{
$specifiedDscResources = $options['DscResource'] |
- Microsoft.PowerShell.Utility\Select-Object -Unique |
+ Microsoft.PowerShell.Utility\Select-Object -Unique -ErrorAction Ignore |
Microsoft.PowerShell.Core\ForEach-Object {"$($script:DscResource)_$_"}
}
@@ -9501,7 +9540,7 @@ function Find-Package
if($options.ContainsKey('RoleCapability'))
{
$specifiedRoleCapabilities = $options['RoleCapability'] |
- Microsoft.PowerShell.Utility\Select-Object -Unique |
+ Microsoft.PowerShell.Utility\Select-Object -Unique -ErrorAction Ignore |
Microsoft.PowerShell.Core\ForEach-Object {"$($script:RoleCapability)_$_"}
}
@@ -9509,7 +9548,7 @@ function Find-Package
if($options.ContainsKey('Command'))
{
$specifiedCommands = $options['Command'] |
- Microsoft.PowerShell.Utility\Select-Object -Unique |
+ Microsoft.PowerShell.Utility\Select-Object -Unique -ErrorAction Ignore |
Microsoft.PowerShell.Core\ForEach-Object {"$($script:Command)_$_"}
}
@@ -9517,7 +9556,7 @@ function Find-Package
if($options.ContainsKey('Includes'))
{
$includes = $options['Includes'] |
- Microsoft.PowerShell.Utility\Select-Object -Unique |
+ Microsoft.PowerShell.Utility\Select-Object -Unique -ErrorAction Ignore |
Microsoft.PowerShell.Core\ForEach-Object {"$($script:Includes)_$_"}
# Add PSIncludes_DscResource to $specifiedIncludes iff -DscResource names are not specified
@@ -10432,7 +10471,7 @@ function Install-PackageUtility
}
}
- $InstalledItemsList | Select-Object -Unique
+ $InstalledItemsList | Select-Object -Unique -ErrorAction Ignore
if($Debug)
{
@@ -10518,11 +10557,11 @@ function Install-PackageUtility
$InstalledItemDetails = $null
if($packageType -eq $script:PSArtifactTypeModule)
{
- $InstalledItemDetails = Get-InstalledModuleDetails -Name $pkg.Name | Select-Object -Last 1
+ $InstalledItemDetails = Get-InstalledModuleDetails -Name $pkg.Name | Select-Object -Last 1 -ErrorAction Ignore
}
elseif($packageType -eq $script:PSArtifactTypeScript)
{
- $InstalledItemDetails = Get-InstalledScriptDetails -Name $pkg.Name | Select-Object -Last 1
+ $InstalledItemDetails = Get-InstalledScriptDetails -Name $pkg.Name | Select-Object -Last 1 -ErrorAction Ignore
}
if($InstalledItemDetails -and
@@ -10947,7 +10986,7 @@ function Uninstall-Package
if(-not $Force -and $dependentModules)
{
- $message = $LocalizedData.UnableToUninstallAsOtherModulesNeedThisModule -f ($moduleName, $version, $moduleBase, $(($dependentModules.Name | Select-Object -Unique) -join ','), $moduleName)
+ $message = $LocalizedData.UnableToUninstallAsOtherModulesNeedThisModule -f ($moduleName, $version, $moduleBase, $(($dependentModules.Name | Select-Object -Unique -ErrorAction Ignore) -join ','), $moduleName)
ThrowError -ExceptionName "System.InvalidOperationException" `
-ExceptionMessage $message `
@@ -11093,7 +11132,7 @@ function Uninstall-Package
($scriptName,
$version,
$scriptBase,
- $(($dependentScriptNames | Select-Object -Unique) -join ','),
+ $(($dependentScriptNames | Select-Object -Unique -ErrorAction Ignore) -join ','),
$scriptName)
ThrowError -ExceptionName 'System.InvalidOperationException' `
@@ -11638,11 +11677,20 @@ function Set-InstalledModulesVariable
foreach ($location in $modulePaths)
{
# find all modules installed using PowerShellGet
- $moduleBases = Get-ChildItem $location -Recurse `
- -Attributes Hidden -Filter $script:PSGetItemInfoFileName `
- -ErrorAction SilentlyContinue `
- -WarningAction SilentlyContinue `
- | Foreach-Object { $_.Directory }
+ $GetChildItemParams = @{
+ Path = $location
+ Recurse = $true
+ Filter = $script:PSGetItemInfoFileName
+ ErrorAction = 'SilentlyContinue'
+ WarningAction = 'SilentlyContinue'
+ }
+
+ if(IsWindows)
+ {
+ $GetChildItemParams['Attributes'] = 'Hidden'
+ }
+
+ $moduleBases = Get-ChildItem @GetChildItemParams | Foreach-Object { $_.Directory }
foreach ($moduleBase in $moduleBases)
@@ -12170,7 +12218,7 @@ function Test-ModuleInstalled
# Check if module is already installed
$availableModule = Microsoft.PowerShell.Core\Get-Module -ListAvailable -Name $Name -Verbose:$false |
Microsoft.PowerShell.Core\Where-Object {-not (Test-ModuleSxSVersionSupport) -or -not $RequiredVersion -or ($RequiredVersion -eq $_.Version)} |
- Microsoft.PowerShell.Utility\Select-Object -Unique
+ Microsoft.PowerShell.Utility\Select-Object -Unique -ErrorAction Ignore
return $availableModule
}
@@ -12231,7 +12279,7 @@ function Test-ScriptInstalled
$scriptInfo = $scriptInfos | Microsoft.PowerShell.Core\Where-Object {
(-not $RequiredVersion) -or ($RequiredVersion -eq $_.Version)
- } | Microsoft.PowerShell.Utility\Select-Object -First 1
+ } | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
return $scriptInfo
}
@@ -13638,6 +13686,10 @@ function Validate-ModuleAuthenticodeSignature
[string]
$InstallLocation,
+ [Parameter()]
+ [Switch]
+ $IsUpdateOperation,
+
[Parameter()]
[Switch]
$SkipPublisherCheck
@@ -13652,21 +13704,13 @@ function Validate-ModuleAuthenticodeSignature
}
# Skip the publisher check when -SkipPublisherCheck is specified and
- # previously-installed module is not signed by Microsoft
- if($SkipPublisherCheck)
+ # it is not an update operation.
+ if(-not $IsUpdateOperation -and $SkipPublisherCheck)
{
- if($InstalledModuleDetails -and $InstalledModuleDetails.IsMicrosoftCertificate)
- {
- $Message = $LocalizedData.ProceedingWithPublisherCheckForMicrosoftModules -f ($CurrentModuleInfo.Name)
- Write-Verbose -Message $message
- }
- else
- {
- $Message = $LocalizedData.SkippingPublisherCheck -f ($CurrentModuleInfo.Version, $CurrentModuleInfo.Name)
- Write-Verbose -Message $message
+ $Message = $LocalizedData.SkippingPublisherCheck -f ($CurrentModuleInfo.Version, $CurrentModuleInfo.Name)
+ Write-Verbose -Message $message
- return $true
- }
+ return $true
}
# Validate the catalog signature for the current module being installed.
@@ -13744,7 +13788,7 @@ function Validate-ModuleAuthenticodeSignature
}
else
{
- $Message = $LocalizedData.PublishersMismatch -f ($CurrentModuleAuthenticodePublisher, $CurrentModuleInfo.Name, $CurrentModuleInfo.Version, $InstalledModuleAuthenticodePublisher, $InstalledModuleInfo.Name, $InstalledModuleVersion)
+ $Message = $LocalizedData.PublishersMismatch -f ($InstalledModuleInfo.Name, $InstalledModuleVersion, $CurrentModuleInfo.Name, $CurrentModuleAuthenticodePublisher, $CurrentModuleInfo.Version)
ThrowError -ExceptionName 'System.InvalidOperationException' `
-ExceptionMessage $message `
-ErrorId 'PublishersMismatch' `
@@ -13828,7 +13872,7 @@ function Validate-ModuleCommandAlreadyAvailable
-WarningAction SilentlyContinue |
Microsoft.PowerShell.Core\Where-Object { ($CommandNames -contains $_.Name) -and
($_.Source -ne $CurrentModuleInfo.Name) } |
- Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction SilentlyContinue
+ Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if($AvailableCommand)
{
$message = $LocalizedData.ModuleCommandAlreadyAvailable -f ($AvailableCommand.Name, $CurrentModuleInfo.Name)
@@ -14003,11 +14047,11 @@ function Get-InstalledModuleAuthenticodeSignature
$SourceModule = $AvailableModules | Microsoft.PowerShell.Core\Where-Object {
$_.ModuleBase.StartsWith($InstallLocation, [System.StringComparison]::OrdinalIgnoreCase)
- } | Microsoft.PowerShell.Utility\Select-Object -First 1
+ } | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
if(-not $SourceModule)
{
- $SourceModule = $AvailableModules | Microsoft.PowerShell.Utility\Select-Object -First 1
+ $SourceModule = $AvailableModules | Microsoft.PowerShell.Utility\Select-Object -First 1 -ErrorAction Ignore
}
else
{
@@ -14130,10 +14174,11 @@ function Test-ValidManifestModule
-CallerPSCmdlet $PSCmdlet `
-ErrorCategory InvalidOperation
}
- else
+ elseif(IsWindows)
{
$ValidationResult = Validate-ModuleAuthenticodeSignature -CurrentModuleInfo $PSModuleInfo `
-InstallLocation $InstallLocation `
+ -IsUpdateOperation:$IsUpdateOperation `
-SkipPublisherCheck:$SkipPublisherCheck
if($ValidationResult)
diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
index 063ba49dd..eb9d3b262 100644
--- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
+++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
@@ -354,6 +354,18 @@ namespace System.Management.Automation
}
}
+ internal static string NonWindowsGetUserFromPid(int path)
+ {
+ if (!IsWindows)
+ {
+ return LinuxPlatform.GetUserFromPid(path);
+ }
+ else
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+
#if CORECLR
internal static string NonWindowsGetFolderPath(SpecialFolder folder)
{
@@ -740,6 +752,11 @@ namespace System.Management.Automation
return Native.FollowSymLink(path);
}
+ public static string GetUserFromPid(int pid)
+ {
+ return Native.GetUserFromPid(pid);
+ }
+
[StructLayout(LayoutKind.Sequential)]
internal class SetDateInfoInternal
{
@@ -809,6 +826,9 @@ namespace System.Management.Automation
[return: MarshalAs(UnmanagedType.LPStr)]
internal static extern string FollowSymLink([MarshalAs(UnmanagedType.LPStr)]string filePath);
+ [DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.LPStr)]
+ internal static extern string GetUserFromPid(int pid);
}
}
diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs
index a9d4f2aaf..8dd8f96be 100644
--- a/src/System.Management.Automation/engine/Utils.cs
+++ b/src/System.Management.Automation/engine/Utils.cs
@@ -960,21 +960,20 @@ namespace System.Management.Automation
internal static bool IsAdministrator()
{
- // Porting note: only Windows supports the
- // SecurityPrincipal API of .net for now assume Linux
- // users have no concept of "Administrator" for now and
- // specifically do not map to a check for root user
- if (Platform.IsWindows)
- {
- System.Security.Principal.WindowsIdentity currentIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
- System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(currentIdentity);
+ // Porting note: only Windows supports the SecurityPrincipal API of .NET. Due to
+ // advanced privilege models, the correct approach on Unix is to assume the user has
+ // permissions, attempt the task, and error gracefully if the task fails due to
+ // permissions. To fit into PowerShell's existing model of pre-emptively checking
+ // permissions (which cannot be assumed on Unix), we "assume" the user is an
+ // administrator by returning true, thus nullifying this check on Unix.
+#if LINUX
+ return true;
+#else
+ System.Security.Principal.WindowsIdentity currentIdentity = System.Security.Principal.WindowsIdentity.GetCurrent();
+ System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(currentIdentity);
- return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
- }
- else
- {
- return false;
- }
+ return principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
+#endif
}
internal static bool NativeItemExists(string path)
diff --git a/src/libpsl-native/src/CMakeLists.txt b/src/libpsl-native/src/CMakeLists.txt
index 5b2ec2419..8b3d0f19a 100644
--- a/src/libpsl-native/src/CMakeLists.txt
+++ b/src/libpsl-native/src/CMakeLists.txt
@@ -1,4 +1,8 @@
add_library(psl-native SHARED
+ getstat.cpp
+ getpwuid.cpp
+ getuserfrompid.cpp
+ getfileowner.cpp
getcurrentprocessorid.cpp
getusername.cpp
getcomputername.cpp
diff --git a/src/libpsl-native/src/followsymlink.cpp b/src/libpsl-native/src/followsymlink.cpp
index 40e12b9b7..46576256e 100644
--- a/src/libpsl-native/src/followsymlink.cpp
+++ b/src/libpsl-native/src/followsymlink.cpp
@@ -7,6 +7,7 @@
#include
#include
#include "followsymlink.h"
+#include "issymlink.h"
//! @brief Followsymlink determines target path of a sym link
//!
@@ -36,18 +37,31 @@
char* FollowSymLink(const char* fileName)
{
- errno = 0;
+ errno = 0;
- // Check parameters
if (!fileName)
{
errno = ERROR_INVALID_PARAMETER;
return NULL;
}
- char buffer[PATH_MAX];
- ssize_t sz = readlink(fileName, buffer, PATH_MAX);
+ // return null for non symlinks
+ if (!IsSymLink(fileName))
+ {
+ return NULL;
+ }
+ // attempt to resolve with the absolute file path
+ char buffer[PATH_MAX];
+ char* realPath = realpath(fileName, buffer);
+
+ if (realPath)
+ {
+ return strndup(realPath, strlen(realPath) + 1);
+ }
+
+ // if the path wasn't resolved, use readlink
+ ssize_t sz = readlink(fileName, buffer, PATH_MAX);
if (sz == -1)
{
switch(errno)
@@ -60,7 +74,6 @@ char* FollowSymLink(const char* fileName)
break;
case EINVAL:
errno = ERROR_INVALID_NAME;
- break;
case EIO:
errno = ERROR_GEN_FAILURE;
break;
@@ -82,6 +95,7 @@ char* FollowSymLink(const char* fileName)
default:
errno = ERROR_INVALID_FUNCTION;
}
+
return NULL;
}
diff --git a/src/libpsl-native/src/getfileowner.cpp b/src/libpsl-native/src/getfileowner.cpp
new file mode 100644
index 000000000..c1bd5b69f
--- /dev/null
+++ b/src/libpsl-native/src/getfileowner.cpp
@@ -0,0 +1,50 @@
+//! @file getfileowner.cpp
+//! @author Andrew Schwartzmeyer
+//! @brief returns the owner of a file
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "getstat.h"
+#include "getpwuid.h"
+#include "getfileowner.h"
+
+//! @brief GetFileOwner returns the owner of a file
+//!
+//! GetFileOwner
+//!
+//! @param[in] fileName
+//! @parblock
+//! A pointer to the buffer that contains the file name
+//!
+//! char* is marshaled as an LPStr, which on Linux is UTF-8.
+//! @endparblock
+//!
+//! @exception errno Passes this error via errno to GetLastError:
+//! - ERROR_INVALID_PARAMETER: parameter is not valid
+//!
+//! @retval file owner, or NULL if unsuccessful
+//!
+char* GetFileOwner(const char* fileName)
+{
+ int32_t ret = 0;
+ errno = 0;
+
+ if (!fileName)
+ {
+ errno = ERROR_INVALID_PARAMETER;
+ return NULL;
+ }
+
+ struct stat buf;
+ ret = GetStat(fileName, &buf);
+ if (ret != 0)
+ {
+ return NULL;
+ }
+
+ return GetPwUid(buf.st_uid);
+}
diff --git a/src/libpsl-native/src/getfileowner.h b/src/libpsl-native/src/getfileowner.h
new file mode 100644
index 000000000..7da83dccc
--- /dev/null
+++ b/src/libpsl-native/src/getfileowner.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "pal.h"
+
+PAL_BEGIN_EXTERNC
+
+char* GetFileOwner(const char* fileName);
+
+PAL_END_EXTERNC
diff --git a/src/libpsl-native/src/getfullyqualifiedname.cpp b/src/libpsl-native/src/getfullyqualifiedname.cpp
index 44178a0a5..bc4fe1b19 100644
--- a/src/libpsl-native/src/getfullyqualifiedname.cpp
+++ b/src/libpsl-native/src/getfullyqualifiedname.cpp
@@ -27,12 +27,12 @@ char *GetFullyQualifiedName()
char *computerName = GetComputerName();
if (computerName == NULL)
{
- return NULL;
+ return NULL;
}
if (strchr(computerName, '.') != NULL)
{
- return computerName;
+ return computerName;
}
struct addrinfo hints, *info;
@@ -51,7 +51,7 @@ char *GetFullyQualifiedName()
if (getaddrinfo(computerName, "http", &hints, &info) != 0)
{
errno = ERROR_BAD_NET_NAME;
- return NULL;
+ return NULL;
}
// info is actually a link-list. We'll just return the first full name
diff --git a/src/libpsl-native/src/getpwuid.cpp b/src/libpsl-native/src/getpwuid.cpp
new file mode 100644
index 000000000..62263067e
--- /dev/null
+++ b/src/libpsl-native/src/getpwuid.cpp
@@ -0,0 +1,81 @@
+//! @file getpwuid.cpp
+//! @author Andrew Schwartzmeyer
+//! @brief returns the username for a uid
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "getpwuid.h"
+
+//! @brief GetPwUid returns the username for a uid
+//!
+//! GetPwUid
+//!
+//! @param[in] uid
+//! @parblock
+//! The user identifier to lookup.
+//! @endparblock
+//!
+//! @exception errno Passes these errors via errno to GetLastError:
+//! - ERROR_NO_SUCH_USER: user lookup unsuccessful
+//! - ERROR_OUTOFMEMORY insufficient kernel memory
+//! - ERROR_GEN_FAILURE: anything else
+//!
+//! @retval username as UTF-8 string, or NULL if unsuccessful
+//!
+char* GetPwUid(uid_t uid)
+{
+ int32_t ret = 0;
+ struct passwd pwd;
+ struct passwd* result = NULL;
+ char* buf;
+
+ int buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (buflen < 1)
+ {
+ buflen = 2048;
+ }
+
+allocate:
+ buf = (char*)calloc(buflen, sizeof(char));
+
+ ret = getpwuid_r(uid, &pwd, buf, buflen, &result);
+
+ if (ret != 0)
+ {
+ switch(errno)
+ {
+ case ERANGE:
+ free(buf);
+ buflen *= 2;
+ goto allocate;
+ case ENOENT:
+ case ESRCH:
+ case EBADF:
+ case EPERM:
+ errno = ERROR_NO_SUCH_USER;
+ break;
+ case ENOMEM:
+ errno = ERROR_OUTOFMEMORY;
+ break;
+ default:
+ errno = ERROR_GEN_FAILURE;
+ }
+ return NULL;
+ }
+
+ // no result
+ if (result == NULL)
+ {
+ return NULL;
+ }
+
+ // allocate copy on heap so CLR can free it
+ size_t userlen = strnlen(pwd.pw_name, buflen);
+ char* username = strndup(pwd.pw_name, userlen);
+ free(buf);
+ return username;
+}
diff --git a/src/libpsl-native/src/getpwuid.h b/src/libpsl-native/src/getpwuid.h
new file mode 100644
index 000000000..cbf2cca91
--- /dev/null
+++ b/src/libpsl-native/src/getpwuid.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include
+#include "pal.h"
+
+PAL_BEGIN_EXTERNC
+
+char* GetPwUid(uid_t uid);
+
+PAL_END_EXTERNC
diff --git a/src/libpsl-native/src/getstat.cpp b/src/libpsl-native/src/getstat.cpp
new file mode 100644
index 000000000..3e89d1a6e
--- /dev/null
+++ b/src/libpsl-native/src/getstat.cpp
@@ -0,0 +1,96 @@
+//! @file getstat.cpp
+//! @author Andrew Schwartzmeyer
+//! @brief returns the stat of a file
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include "getstat.h"
+
+//! @brief GetStat returns the stat of a file. This simply delegates to the
+//! stat() system call and maps errno to the expected values for GetLastError.
+//!
+//! GetStat
+//!
+//! @param[in] path
+//! @parblock
+//! A pointer to the buffer that contains the file name
+//!
+//! char* is marshaled as an LPStr, which on Linux is UTF-8.
+//! @endparblock
+//!
+//! @param[in] stat
+//! @parblock
+//! A pointer to the buffer in which to place the stat information
+//! @endparblock
+//!
+//! @exception errno Passes these errors via errno to GetLastError:
+//! - ERROR_INVALID_PARAMETER: parameter is not valid
+//! - ERROR_FILE_NOT_FOUND: file does not exist
+//! - ERROR_ACCESS_DENIED: access is denied
+//! - ERROR_INVALID_ADDRESS: attempt to access invalid address
+//! - ERROR_STOPPED_ON_SYMLINK: too many symbolic links
+//! - ERROR_GEN_FAILURE: I/O error occurred
+//! - ERROR_INVALID_NAME: file provided is not a symbolic link
+//! - ERROR_INVALID_FUNCTION: incorrect function
+//! - ERROR_BAD_PATH_NAME: pathname is too long
+//! - ERROR_OUTOFMEMORY insufficient kernel memory
+//!
+//! @retval 0 if successful
+//! @retval -1 if failed
+//!
+
+int32_t GetStat(const char* path, struct stat* buf)
+{
+ errno = 0;
+
+ if (!path)
+ {
+ errno = ERROR_INVALID_PARAMETER;
+ return -1;
+ }
+
+ int32_t ret = stat(path, buf);
+
+ if (ret != 0)
+ {
+ switch(errno)
+ {
+ case EACCES:
+ errno = ERROR_ACCESS_DENIED;
+ break;
+ case EBADF:
+ errno = ERROR_FILE_NOT_FOUND;
+ break;
+ case EFAULT:
+ errno = ERROR_INVALID_ADDRESS;
+ break;
+ case ELOOP:
+ errno = ERROR_STOPPED_ON_SYMLINK;
+ break;
+ case ENAMETOOLONG:
+ errno = ERROR_GEN_FAILURE;
+ break;
+ case ENOENT:
+ errno = ERROR_FILE_NOT_FOUND;
+ break;
+ case ENOMEM:
+ errno = ERROR_NO_SUCH_USER;
+ break;
+ case ENOTDIR:
+ errno = ERROR_INVALID_NAME;
+ break;
+ case EOVERFLOW:
+ errno = ERROR_BUFFER_OVERFLOW;
+ break;
+ default:
+ errno = ERROR_INVALID_FUNCTION;
+ }
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/libpsl-native/src/getstat.h b/src/libpsl-native/src/getstat.h
new file mode 100644
index 000000000..00bc26e9f
--- /dev/null
+++ b/src/libpsl-native/src/getstat.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include
+#include "pal.h"
+
+PAL_BEGIN_EXTERNC
+
+int32_t GetStat(const char* path, struct stat* buf);
+
+PAL_END_EXTERNC
diff --git a/src/libpsl-native/src/getuserfrompid.cpp b/src/libpsl-native/src/getuserfrompid.cpp
new file mode 100644
index 000000000..fd148a9ff
--- /dev/null
+++ b/src/libpsl-native/src/getuserfrompid.cpp
@@ -0,0 +1,47 @@
+#include
+#include
+#include
+#include
+#include "pal.h"
+#include "getfileowner.h"
+#include "getpwuid.h"
+#include "getuserfrompid.h"
+
+char* GetUserFromPid(pid_t pid)
+{
+
+#if defined(__linux__)
+
+ // Get effective owner of pid from procfs
+ std::stringstream ss;
+ ss << "/proc/" << pid;
+ std::string path;
+ ss >> path;
+
+ return GetFileOwner(path.c_str());
+
+#elif defined(__APPLE__) && defined(__MACH__)
+
+ // Get effective owner of pid from sysctl
+ struct kinfo_proc oldp;
+ size_t oldlenp = sizeof(oldp);
+ int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+ u_int namelen = sizeof(name)/sizeof(int);
+
+ // Read-only query
+ int ret = sysctl(name, namelen, &oldp, &oldlenp, NULL, 0);
+ if (ret != 0 || oldlenp == 0)
+ {
+ errno = ERROR_GEN_FAILURE;
+ return NULL;
+ }
+
+ return GetPwUid(oldp.kp_eproc.e_ucred.cr_uid);
+
+#else
+
+ return NULL;
+
+#endif
+
+}
diff --git a/src/libpsl-native/src/getuserfrompid.h b/src/libpsl-native/src/getuserfrompid.h
new file mode 100644
index 000000000..c019f475f
--- /dev/null
+++ b/src/libpsl-native/src/getuserfrompid.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "pal.h"
+
+PAL_BEGIN_EXTERNC
+
+char* GetUserFromPid(pid_t pid);
+
+PAL_END_EXTERNC
diff --git a/src/libpsl-native/src/getusername.cpp b/src/libpsl-native/src/getusername.cpp
index 0efab9668..56c3d6d08 100644
--- a/src/libpsl-native/src/getusername.cpp
+++ b/src/libpsl-native/src/getusername.cpp
@@ -2,64 +2,16 @@
//! @author Andrew Schwartzmeyer
//! @brief Implements GetUserName for Linux
-#include
-#include
#include
-#include
-#include
+#include "getpwuid.h"
#include "getusername.h"
//! @brief GetUserName retrieves the name of the user associated with
//! the current thread.
//!
-//! @exception errno Passes these errors via errno to GetLastError:
-//! - ERROR_INVALID_PARAMETER: parameter is not valid
-//! - ERROR_NO_SUCH_USER: there was no corresponding user
-//! - ERROR_GEN_FAILURE: sysconf() or getpwuid() failed for unknown reasons
-//!
//! @retval username as UTF-8 string, or null if unsuccessful
char* GetUserName()
{
- errno = 0;
-
- struct passwd pwd;
- struct passwd* result;
- // gets the initial suggested size for buf
- int buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
- if (buflen == -1)
- {
- errno = ERROR_GEN_FAILURE;
- return NULL;
- }
- std::string buf(buflen, 0);
-
// geteuid() gets the effective user ID of the calling process, and is always successful
- int ret = getpwuid_r(geteuid(), &pwd, &buf[0], buflen, &result);
-
- // Map errno to Win32 Error Codes
- if (ret)
- {
- switch (errno)
- {
- case ENOENT:
- case ESRCH:
- case EBADF:
- case EPERM:
- errno = ERROR_NO_SUCH_USER;
- break;
- default:
- errno = ERROR_GEN_FAILURE;
- }
- return NULL;
- }
-
- // Check if no user matched
- if (result == NULL)
- {
- errno = ERROR_NO_SUCH_USER;
- return NULL;
- }
-
- // allocate copy on heap so CLR can free it
- return strdup(result->pw_name);
+ return GetPwUid(geteuid());
}
diff --git a/src/libpsl-native/test/CMakeLists.txt b/src/libpsl-native/test/CMakeLists.txt
index 4ddc53836..ed4440d2a 100644
--- a/src/libpsl-native/test/CMakeLists.txt
+++ b/src/libpsl-native/test/CMakeLists.txt
@@ -1,7 +1,9 @@
add_subdirectory(googletest)
add_executable(psl-native-test
+ test-getfileowner.cpp
test-locale.cpp
+ test-getuserfrompid.cpp
test-getcurrentprocessid.cpp
test-getusername.cpp
test-getcomputername.cpp
diff --git a/src/libpsl-native/test/test-createsymlink.cpp b/src/libpsl-native/test/test-createsymlink.cpp
index abc0646c6..7bb9997b3 100644
--- a/src/libpsl-native/test/test-createsymlink.cpp
+++ b/src/libpsl-native/test/test-createsymlink.cpp
@@ -32,19 +32,19 @@ protected:
// First create a temp file
int fd = mkstemp(fileTemplateBuf);
EXPECT_TRUE(fd != -1);
- file = fileTemplateBuf;
+ file = fileTemplateBuf;
- // Create a temp directory
- dir = mkdtemp(dirTemplateBuf);
+ // Create a temp directory
+ dir = mkdtemp(dirTemplateBuf);
EXPECT_TRUE(dir != NULL);
// Create symbolic link to file
- int ret1 = CreateSymLink(fileSymLink.c_str(), file);
- EXPECT_EQ(ret1, 1);
-
+ int ret1 = CreateSymLink(fileSymLink.c_str(), file);
+ EXPECT_EQ(ret1, 1);
+
// Create symbolic link to directory
- int ret2 = CreateSymLink(dirSymLink.c_str(), dir);
- EXPECT_EQ(ret2, 1);
+ int ret2 = CreateSymLink(dirSymLink.c_str(), dir);
+ EXPECT_EQ(ret2, 1);
}
~CreateSymLinkTest()
@@ -52,16 +52,16 @@ protected:
int ret;
ret = unlink(fileSymLink.c_str());
- EXPECT_EQ(0, ret);
+ EXPECT_EQ(0, ret);
ret = unlink(dirSymLink.c_str());
- EXPECT_EQ(0, ret);
+ EXPECT_EQ(0, ret);
ret = unlink(file);
- EXPECT_EQ(0, ret);
+ EXPECT_EQ(0, ret);
- ret = rmdir(dir);
- EXPECT_EQ(0, ret);
+ ret = rmdir(dir);
+ EXPECT_EQ(0, ret);
}
};
@@ -77,7 +77,7 @@ TEST_F(CreateSymLinkTest, FilePathNameDoesNotExist)
std::string invalidFile = "/tmp/symlinktest_invalidFile";
std::string invalidLink = "/tmp/symlinktest_invalidLink";
- // make sure neither exists
+ // make sure neither exists
unlink(invalidFile.c_str());
unlink(invalidLink.c_str());
@@ -97,7 +97,9 @@ TEST_F(CreateSymLinkTest, SymLinkToFile)
EXPECT_EQ(1, retVal);
std::string target = FollowSymLink(fileSymLink.c_str());
- EXPECT_EQ(target, file);
+ char buffer[PATH_MAX];
+ std::string expected = realpath(file, buffer);
+ EXPECT_EQ(target, expected);
}
TEST_F(CreateSymLinkTest, SymLinkToDirectory)
@@ -106,7 +108,9 @@ TEST_F(CreateSymLinkTest, SymLinkToDirectory)
EXPECT_EQ(1, retVal);
std::string target = FollowSymLink(dirSymLink.c_str());
- EXPECT_EQ(target, dir);
+ char buffer[PATH_MAX];
+ std::string expected = realpath(dir, buffer);
+ EXPECT_EQ(target, expected);
}
TEST_F(CreateSymLinkTest, SymLinkAgain)
diff --git a/src/libpsl-native/test/test-getfileowner.cpp b/src/libpsl-native/test/test-getfileowner.cpp
new file mode 100644
index 000000000..275c63ed1
--- /dev/null
+++ b/src/libpsl-native/test/test-getfileowner.cpp
@@ -0,0 +1,25 @@
+//! @file test-getfileowner.cpp
+//! @author Andrew Schwartzmeyer
+//! @brief Tests GetFileOwner
+
+#include
+#include
+#include
+#include "getfileowner.h"
+
+TEST(GetFileOwnerTest, CanGetOwnerOfRoot)
+{
+ EXPECT_STREQ(GetFileOwner("/"), "root");
+}
+
+TEST(GetFileOwnerTest, CannotGetOwnerOfFakeFile)
+{
+ EXPECT_STREQ(GetFileOwner("SomeMadeUpFileNameThatDoesNotExist"), NULL);
+ EXPECT_EQ(errno, ERROR_FILE_NOT_FOUND);
+}
+
+TEST(GetFileOwnerTest, ReturnsNullForNullInput)
+{
+ EXPECT_STREQ(GetFileOwner(NULL), NULL);
+ EXPECT_EQ(errno, ERROR_INVALID_PARAMETER);
+}
diff --git a/src/libpsl-native/test/test-getfullyqualifiedname.cpp b/src/libpsl-native/test/test-getfullyqualifiedname.cpp
index 7c0353cd1..94c2947d6 100644
--- a/src/libpsl-native/test/test-getfullyqualifiedname.cpp
+++ b/src/libpsl-native/test/test-getfullyqualifiedname.cpp
@@ -3,38 +3,35 @@
//! @brief Unit tests for GetFullyQualifiedName
#include
+#include "getcomputername.h"
#include "getfullyqualifiedname.h"
#include
#include
#include
#include
-//! Test fixture for GetComputerNameTest
-class GetFullyQualifiedNameTest : public ::testing::Test
-{
-};
-
-TEST_F(GetFullyQualifiedNameTest, ValidateLinuxGetFullyQualifiedDomainName)
+TEST(GetFullyQualifiedNameTest, ValidateLinuxGetFullyQualifiedDomainName)
{
std::string actual(GetFullyQualifiedName());
- std::string hostname(_POSIX_HOST_NAME_MAX, 0);
- ASSERT_FALSE(gethostname(&hostname[0], hostname.length()));
- // trim null characters from string
- hostname = std::string(hostname.c_str());
+ std::string hostname(GetComputerName());
- struct addrinfo hints, *info;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_flags = AI_CANONNAME;
- ASSERT_FALSE(getaddrinfo(hostname.c_str(), "http", &hints, &info));
+ // FQDN starts with hostname (which may be fully qualified)
+ EXPECT_LE(hostname, actual);
- // Compare hostname part of FQDN
- ASSERT_EQ(hostname, actual.substr(0, hostname.length()));
+ // hostname did not include domain name, so test it separately
+ if (hostname < actual)
+ {
+ struct addrinfo hints, *info;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_CANONNAME;
+ EXPECT_FALSE(getaddrinfo(hostname.c_str(), "http", &hints, &info));
- // Compare canonical name to FQDN
- ASSERT_EQ(info->ai_canonname, actual);
+ // Compare canonical name to FQDN
+ EXPECT_STREQ(info->ai_canonname, actual.c_str());
- freeaddrinfo(info);
+ freeaddrinfo(info);
+ }
}
diff --git a/src/libpsl-native/test/test-getuserfrompid.cpp b/src/libpsl-native/test/test-getuserfrompid.cpp
new file mode 100644
index 000000000..ca6d866c6
--- /dev/null
+++ b/src/libpsl-native/test/test-getuserfrompid.cpp
@@ -0,0 +1,13 @@
+//! @file test-getuserfrompid.cpp
+//! @author Andrew Schwartzmeyer
+//! @brief Unit tests for GetUserFromPid
+
+#include
+#include
+#include "getuserfrompid.h"
+
+TEST(GetUserFromPid, Success)
+{
+ char* expected = getpwuid(geteuid())->pw_name;
+ EXPECT_STREQ(GetUserFromPid(getpid()), expected);
+}
diff --git a/src/libpsl-native/test/test-getusername.cpp b/src/libpsl-native/test/test-getusername.cpp
index 2ccb29f68..3f4751276 100644
--- a/src/libpsl-native/test/test-getusername.cpp
+++ b/src/libpsl-native/test/test-getusername.cpp
@@ -2,9 +2,6 @@
//! @author Andrew Schwartzmeyer
//! @brief Unit tests for GetUserName
-#include
-#include
-#include
#include
#include
#include "getusername.h"
@@ -12,6 +9,5 @@
TEST(GetUserName, Success)
{
char* expected = getpwuid(geteuid())->pw_name;
- ASSERT_TRUE(expected != NULL);
- ASSERT_EQ(GetUserName(), std::string(expected));
+ EXPECT_STREQ(GetUserName(), expected);
}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1
index d2ac52def..64a9f7fe4 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Process.Tests.ps1
@@ -1,4 +1,8 @@
Describe "Get-Process" {
+ It "Should support -IncludeUserName" {
+ (Get-Process powershell -IncludeUserName | Select-Object -First 1).UserName | Should Match $env:USERNAME
+ }
+
# These tests are no good, please replace!
It "Should return a type of Object[] for Get-Process cmdlet" -Pending:$IsOSX {
(Get-Process).GetType().BaseType | Should Be 'array'
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1
index 570b42565..593f892ad 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1
@@ -36,7 +36,7 @@ Describe "New-Item" {
It "Should create a file without error" {
New-Item -Name $testfile -Path $tmpDirectory -ItemType file
- Test-Path $FullyQualifiedFile | Should Be $true
+ Test-Path $FullyQualifiedFile | Should Be $true
$fileInfo = Get-ChildItem $FullyQualifiedFile
$fileInfo.Target | Should Be $null
@@ -105,14 +105,14 @@ Describe "New-Item" {
}
It "Should create a symbolic link of a file without error" {
- New-Item -Name $testfile -Path $tmpDirectory -ItemType file
- Test-Path $FullyQualifiedFile | Should Be $true
+ New-Item -Name $testfile -Path $tmpDirectory -ItemType file
+ Test-Path $FullyQualifiedFile | Should Be $true
- New-Item -ItemType SymbolicLink -Target $FullyQualifiedFile -Name $testlink -Path $tmpDirectory
- Test-Path $FullyQualifiedLink | Should Be $true
+ New-Item -ItemType SymbolicLink -Target $FullyQualifiedFile -Name $testlink -Path $tmpDirectory
+ Test-Path $FullyQualifiedLink | Should Be $true
$fileInfo = Get-ChildItem $FullyQualifiedLink
- $fileInfo.Target | Should Be $FullyQualifiedFile
+ $fileInfo.Target | Should Match ([regex]::Escape($FullyQualifiedFile))
$fileInfo.LinkType | Should Be "SymbolicLink"
}
@@ -128,18 +128,18 @@ Describe "New-Item" {
}
It "Should create a symbolic link from directory without error" {
- New-Item -Name $testFolder -Path $tmpDirectory -ItemType directory
- Test-Path $FullyQualifiedFolder | Should Be $true
+ New-Item -Name $testFolder -Path $tmpDirectory -ItemType directory
+ Test-Path $FullyQualifiedFolder | Should Be $true
- New-Item -ItemType SymbolicLink -Target $FullyQualifiedFolder -Name $testlink -Path $tmpDirectory
- Test-Path $FullyQualifiedLink | Should Be $true
+ New-Item -ItemType SymbolicLink -Target $FullyQualifiedFolder -Name $testlink -Path $tmpDirectory
+ Test-Path $FullyQualifiedLink | Should Be $true
$fileInfo = Get-ChildItem $FullyQualifiedLink
- $fileInfo.Target | Should Be $FullyQualifiedFolder
+ $fileInfo.Target | Should Match ([regex]::Escape($FullyQualifiedFolder))
$fileInfo.LinkType | Should Be "SymbolicLink"
- # Remove the link explicitly to avoid broken symlink issue
- Remove-Item $FullyQualifiedLink -Force
+ # Remove the link explicitly to avoid broken symlink issue
+ Remove-Item $FullyQualifiedLink -Force
}
It "Should create a hard link of a file without error" {
@@ -154,6 +154,4 @@ Describe "New-Item" {
$fileInfo.LinkType | Should Be "HardLink"
}
-
-
}