Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump PnP PowerShell to v2 #2764

Merged
merged 7 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions MIGRATE-1.0-to-2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Updating from PnP PowerShell 1.x to 2.x

The 2.x version of PnP PowerShell is based exclusively on .NET 6.0, which means that it may not work on older PowerShell versions like PowerShell 5.1 or ISE.

- We had to update the module to .NET 6.0 because Microsoft removed support for .NET 3.1 in early December 2022. So, we had to bump our .NET version.

- The 1.x version of PnP PowerShell was based on .NET Framework 4.6.2 and .NET 3.1.
We decided to drop support for .NET Framework it is not actively developed, doesn't work across-platforms and only receives maintainence and security updates. So, it would add quite a lot of code complexity and maintainabilty issues for us going forward in the future.

The 2.x version of PnP PowerShell will work only on PowerShell 7.2.x or later versions.

## Steps to update from 1.x to 2.x

- Download the PowerShell version from [this link](https://aka.ms/powershell-release?tag=lts)

- For Windows machines, please use [this link](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.2)

- For other operating systems, please refer to [this link](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.2)

Once downloaded, please install it in your environment. After that, you can install the PnP PowerShell module like you used to do.

```powershell
Install-Module -Name "PnP.PowerShell"
```

If you want to install or update to the latest nightly built prerelease of PnP PowerShell, run:

```powershell
Install-Module -Name "PnP.PowerShell" -AllowPrerelease
```

## Changes needed in Azure DevOps/GitHub Actions/Pipelines

If you are using PnP PowerShell in Azure Devops, GitHub Actions or other pipeline infrastructure, you will have to update your PowerShell version from v5 to v7.2.x or later.

Recommend referring to these 2 links:

- [DevOps Snack: Change PowerShell version in YAML](https://microsoft-bitools.blogspot.com/2021/02/devops-snack-change-powershell-version.html)
- [How to enable PowerShell core in Azure Pipeline?](https://theautomationcode.com/how-to-enable-powershell-core-in-azure-pipeline/)

## Breaking changes

- `Export-PnPTaxonomy` cmdlet does not support export of taxonomy using `UTF-7` encoding. If `UTF-7` is specified, it will switch to `UTF-8` encoding.
- There are no other breaking changes.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# PnP PowerShell

**PnP PowerShell** is a .NET 6 / .NET Framework 4.6.2 based PowerShell Module providing over 650 cmdlets that work with Microsoft 365 environments such as SharePoint Online, Microsoft Teams, Microsoft Project, Security & Compliance, Azure Active Directory, and more.
**PnP PowerShell** is a .NET 6 based PowerShell Module providing over 650 cmdlets that work with Microsoft 365 environments such as SharePoint Online, Microsoft Teams, Microsoft Project, Security & Compliance, Azure Active Directory, and more.

Last version | Last nightly version
-------------|---------------------
Expand All @@ -18,3 +18,7 @@ This library is open-source and community provided library with active community
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

<img src="https://pnptelemetry.azurewebsites.net/pnp-powershell/readme" />

## Updating from 1.x to 2.x

Please refer to [this page](MIGRATE-1.0-to-2.0.md) while performing an update from your 1.x version to 2.x version of PnP PowerShell.
28 changes: 3 additions & 25 deletions build/Build-Debug.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ if ($LASTEXITCODE -eq 0) {

$corePath = "$destinationFolder/Core"
$commonPath = "$destinationFolder/Common"
$frameworkPath = "$destinationFolder/Framework"

$assemblyExceptions = @("System.Memory.dll");

Expand All @@ -109,19 +108,13 @@ if ($LASTEXITCODE -eq 0) {
New-Item -Path $destinationFolder -ItemType Directory -Force | Out-Null
New-Item -Path "$destinationFolder\Core" -ItemType Directory -Force | Out-Null
New-Item -Path "$destinationFolder\Common" -ItemType Directory -Force | Out-Null
if (!$IsLinux -and !$IsMacOs) {
New-Item -Path "$destinationFolder\Framework" -ItemType Directory -Force | Out-Null
}

Write-Host "Copying files to $destinationFolder" -ForegroundColor Yellow

$commonFiles = [System.Collections.Generic.Hashset[string]]::new()
Copy-Item -Path "$PSScriptRoot/../resources/*.ps1xml" -Destination "$destinationFolder"
Get-ChildItem -Path "$PSScriptRoot/../src/ALC/bin/Debug/netstandard2.0" | Where-Object { $_.Extension -in '.dll', '.pdb' } | Foreach-Object { if (!$assemblyExceptions.Contains($_.Name)) { [void]$commonFiles.Add($_.Name) }; Copy-Item -LiteralPath $_.FullName -Destination $commonPath }
Get-ChildItem -Path "$PSScriptRoot/../src/Commands/bin/Debug/$configuration" | Where-Object { $_.Extension -in '.dll', '.pdb' -and -not $commonFiles.Contains($_.Name) } | Foreach-Object { Copy-Item -LiteralPath $_.FullName -Destination $corePath }
if (!$IsLinux -and !$IsMacOs) {
Get-ChildItem -Path "$PSScriptRoot/../src/Commands/bin/Debug/net462" | Where-Object { $_.Extension -in '.dll', '.pdb' -and -not $commonFiles.Contains($_.Name) } | Foreach-Object { Copy-Item -LiteralPath $_.FullName -Destination $frameworkPath }
}
}
Catch {
Write-Error "Cannot copy files to $destinationFolder. Maybe a PowerShell session is still using the module or PS modules are hosted in a OneDrive synced location. In the latter case, manually delete $destinationFolder and try again."
Expand All @@ -140,36 +133,21 @@ if ($LASTEXITCODE -eq 0) {
else {
$destinationFolder = "$documentsFolder/PowerShell/Modules/PnP.PowerShell"
}
if ($PSVersionTable.PSVersion.Major -eq 5) {
Write-Host "Importing Framework version of assembly"
Import-Module -Name "$destinationFolder/Framework/PnP.PowerShell.dll" -DisableNameChecking
}
else {
Write-Host "Importing dotnet core version of assembly"
Import-Module -Name "$destinationFolder/Core/PnP.PowerShell.dll" -DisableNameChecking
}
Write-Host "Importing dotnet core version of assembly"
Import-Module -Name "$destinationFolder/Core/PnP.PowerShell.dll" -DisableNameChecking
$cmdlets = get-command -Module PnP.PowerShell | ForEach-Object { "`"$_`"" }
$cmdlets -Join ","
}
$cmdletsString = Start-ThreadJob -ScriptBlock $scriptBlock | Receive-Job -Wait

$manifest = "@{
NestedModules = if (`$PSEdition -eq 'Core')
{
'Core/PnP.PowerShell.dll'
}
else
{
'Framework/PnP.PowerShell.dll'
}
NestedModules = 'Core/PnP.PowerShell.dll'
ModuleVersion = '$version'
Description = 'Microsoft 365 Patterns and Practices PowerShell Cmdlets'
GUID = '0b0430ce-d799-4f3b-a565-f0dca1f31e17'
Author = 'Microsoft 365 Patterns and Practices'
CompanyName = 'Microsoft 365 Patterns and Practices'
CompatiblePSEditions = @(`"Core`",`"Desktop`")
PowerShellVersion = '5.1'
DotNetFrameworkVersion = '4.6.2'
ProcessorArchitecture = 'None'
FunctionsToExport = '*'
CmdletsToExport = @($cmdletsString)
Expand Down
11 changes: 1 addition & 10 deletions build/Build-Nightly.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -156,22 +156,13 @@ if ($runPublish -eq $true) {

Write-Host "Writing PSD1" -ForegroundColor Yellow
$manifest = "@{
NestedModules = if (`$PSEdition -eq 'Core')
{
'Core/PnP.PowerShell.dll'
}
else
{
'Framework/PnP.PowerShell.dll'
}
NestedModules = 'Core/PnP.PowerShell.dll'
ModuleVersion = '$version'
Description = 'Microsoft 365 Patterns and Practices PowerShell Cmdlets'
GUID = '0b0430ce-d799-4f3b-a565-f0dca1f31e17'
Author = 'Microsoft 365 Patterns and Practices'
CompanyName = 'Microsoft 365 Patterns and Practices'
CompatiblePSEditions = @(`"Core`",`"Desktop`")
PowerShellVersion = '5.1'
DotNetFrameworkVersion = '4.6.2'
ProcessorArchitecture = 'None'
FunctionsToExport = '*'
CmdletsToExport = @($cmdletsString)
Expand Down
30 changes: 2 additions & 28 deletions src/Commands/AzureAD/RegisterAzureADApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ private string GetAuthToken(CmdletMessageWriter messageWriter)

private X509Certificate2 GetCertificate(PSObject record)
{
X509Certificate2 cert;
X509Certificate2 cert = null;
if (ParameterSetName == ParameterSet_EXISTINGCERT)
{
if (!Path.IsPathRooted(CertificatePath))
Expand Down Expand Up @@ -481,39 +481,13 @@ private X509Certificate2 GetCertificate(PSObject record)
}
else
{
#if NETFRAMEWORK
var x500Values = new List<string>();
if (!MyInvocation.BoundParameters.ContainsKey("CommonName"))
{
CommonName = ApplicationName;
}
if (!string.IsNullOrWhiteSpace(CommonName)) x500Values.Add($"CN={CommonName}");
if (!string.IsNullOrWhiteSpace(Country)) x500Values.Add($"C={Country}");
if (!string.IsNullOrWhiteSpace(State)) x500Values.Add($"S={State}");
if (!string.IsNullOrWhiteSpace(Locality)) x500Values.Add($"L={Locality}");
if (!string.IsNullOrWhiteSpace(Organization)) x500Values.Add($"O={Organization}");
if (!string.IsNullOrWhiteSpace(OrganizationUnit)) x500Values.Add($"OU={OrganizationUnit}");

string x500 = string.Join("; ", x500Values);

if (ValidYears < 1 || ValidYears > 30)
{
ValidYears = 10;
}
DateTime validFrom = DateTime.Today;
DateTime validTo = validFrom.AddYears(ValidYears);

byte[] certificateBytes = CertificateHelper.CreateSelfSignCertificatePfx(x500, validFrom, validTo, CertificatePassword);
cert = new X509Certificate2(certificateBytes, CertificatePassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
#else
if (!MyInvocation.BoundParameters.ContainsKey("CommonName"))
{
CommonName = ApplicationName;
}
DateTime validFrom = DateTime.Today;
DateTime validTo = validFrom.AddYears(ValidYears);
cert = CertificateHelper.CreateSelfSignedCertificate(CommonName, Country, State, Locality, Organization, OrganizationUnit, CertificatePassword, CommonName, validFrom, validTo);
#endif
}
var pfxPath = string.Empty;
var cerPath = string.Empty;
Expand Down Expand Up @@ -613,7 +587,7 @@ private void StartConsentFlow(string loginEndPoint, AzureADApp azureApp, string
var waitTime = 60;
// CmdletMessageWriter.WriteFormattedWarning(this, $"Waiting {waitTime} seconds to launch the consent flow in a popup window.\n\nThis wait is required to make sure that Azure AD is able to initialize all required artifacts. You can always navigate to the consent page manually:\n\n{consentUrl}");

var progressRecord = new ProgressRecord(1, "Please wait...", $"Waiting {waitTime} seconds to launch the consent flow in a popup window. This wait is required to make sure that Azure AD is able to initialize all required artifacts.");
var progressRecord = new ProgressRecord(1, "Please wait...", $"Waiting {waitTime} seconds to update the Azure AD and launch the consent flow in a popup window.");

for (var i = 0; i < waitTime; i++)
{
Expand Down
5 changes: 0 additions & 5 deletions src/Commands/Base/ConnectOnline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,7 @@ protected void Connect(ref CancellationToken cancellationToken)
}

// Connection has been established
#if !NETFRAMEWORK
WriteVerbose($"PnP PowerShell Cmdlets ({new SemanticVersion(Assembly.GetExecutingAssembly().GetName().Version)})");
#else
WriteVerbose($"PnP PowerShell Cmdlets ({Assembly.GetExecutingAssembly().GetName().Version})");
#endif


if (connection.Url != null)
{
Expand Down
4 changes: 0 additions & 4 deletions src/Commands/Base/GetChangeLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,7 @@ protected override void ProcessRecord()
url = "https://raw.githubusercontent.com/pnp/powershell/dev/CHANGELOG.md";
}
var assembly = Assembly.GetExecutingAssembly();
#if !NETFRAMEWORK
var currentVersion = new SemanticVersion(assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion);
#else
var currentVersion = new System.Version(((AssemblyFileVersionAttribute)assembly.GetCustomAttribute(typeof(AssemblyFileVersionAttribute))).Version);
#endif
var response = client.GetAsync(url).GetAwaiter().GetResult();
if (response.IsSuccessStatusCode)
{
Expand Down
16 changes: 0 additions & 16 deletions src/Commands/Base/NewAzureCertificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,7 @@ protected override void ProcessRecord()
DateTime validFrom = DateTime.Today;
DateTime validTo = validFrom.AddYears(ValidYears);


#if NETFRAMEWORK
var x500Values = new List<string>();
if (!string.IsNullOrWhiteSpace(CommonName)) x500Values.Add($"CN={CommonName}");
if (!string.IsNullOrWhiteSpace(Country)) x500Values.Add($"C={Country}");
if (!string.IsNullOrWhiteSpace(State)) x500Values.Add($"S={State}");
if (!string.IsNullOrWhiteSpace(Locality)) x500Values.Add($"L={Locality}");
if (!string.IsNullOrWhiteSpace(Organization)) x500Values.Add($"O={Organization}");
if (!string.IsNullOrWhiteSpace(OrganizationUnit)) x500Values.Add($"OU={OrganizationUnit}");

string x500 = string.Join("; ", x500Values);

byte[] certificateBytes = CertificateHelper.CreateSelfSignCertificatePfx(x500, validFrom, validTo, CertificatePassword);
X509Certificate2 certificate = new X509Certificate2(certificateBytes, CertificatePassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
#else
X509Certificate2 certificate = CertificateHelper.CreateSelfSignedCertificate(CommonName, Country, State, Locality, Organization, OrganizationUnit, CertificatePassword, CommonName, validFrom, validTo);
#endif

if (!string.IsNullOrWhiteSpace(OutPfx))
{
Expand Down
1 change: 1 addition & 0 deletions src/Commands/Base/PnPConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ internal static void CleanupCryptoMachineKey(X509Certificate2 certificate)
if (Utilities.OperatingSystem.IsWindows())
{
var privateKey = (certificate.GetRSAPrivateKey() as RSACng)?.Key;
// var privateKey = (certificate.PrivateKey as RSACng)?.Key;
if (privateKey == null)
return;

Expand Down
48 changes: 0 additions & 48 deletions src/Commands/Base/PnPPowerShellModuleInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,57 +14,11 @@ public class PnPPowerShellModuleInitializer : IModuleAssemblyInitializer

private static string s_binCommonPath = Path.Combine(s_binBasePath, "Common");

#if NETFRAMEWORK
private static string s_binFrameworkPath = Path.Combine(s_binBasePath, "Framework");
#endif

public void OnImport()
{
#if NETFRAMEWORK
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly_NetFramework;
#else
AssemblyLoadContext.Default.Resolving += ResolveAssembly_NetCore;
#endif
}

#if NETFRAMEWORK

private static Assembly ResolveAssembly_NetFramework(object sender, ResolveEventArgs args)
{
// In .NET Framework, we must try to resolve ALL assemblies under the dependency dir here
// This essentially means we are combining the .NET Core ALC and resolve events into one here
// Note that:
// - This is not a recommended usage of Assembly.LoadFile()
// - Even doing this will not bypass the GAC

// Parse the assembly name to get the file name
var asmName = new AssemblyName(args.Name);
var dllFileName = $"{asmName.Name}.dll";

// Look for the DLL in our .NET Framework directory
string frameworkAsmPath = Path.Combine(s_binFrameworkPath, dllFileName);
if (File.Exists(frameworkAsmPath))
{
return LoadAssemblyFile_NetFramework(frameworkAsmPath);
}

// Now look in the dependencies directory to resolve .NET Standard dependencies
string commonAsmPath = Path.Combine(s_binCommonPath, dllFileName);
if (File.Exists(commonAsmPath))
{
return LoadAssemblyFile_NetFramework(commonAsmPath);
}

// We've run out of places to look
return null;
}

private static Assembly LoadAssemblyFile_NetFramework(string assemblyPath)
{
return Assembly.LoadFile(assemblyPath);
}

#else

private static Assembly ResolveAssembly_NetCore(
AssemblyLoadContext assemblyLoadContext,
Expand All @@ -80,7 +34,5 @@ private static Assembly LoadAssemblyFile_NetFramework(string assemblyPath)
// Now load the Engine assembly through the dependency ALC, and let it resolve further dependencies automatically
return DependencyAssemblyLoadContext.GetForDirectory(s_binCommonPath).LoadFromAssemblyName(assemblyName);
}

#endif
}
}
18 changes: 0 additions & 18 deletions src/Commands/Model/Teams/TeamSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,23 +128,5 @@ public Team ToTeam(GroupVisibility groupVisibility)
Visibility = groupVisibility
};
}

public string ToJsonString()
{
#if NETFRAMEWORK
return JsonSerializer.Serialize(this, new JsonSerializerOptions()
{
IgnoreNullValues = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

#else
return JsonSerializer.Serialize(this, new JsonSerializerOptions()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
#endif
}
}
}
9 changes: 2 additions & 7 deletions src/Commands/Pages/ConvertToPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -477,13 +477,8 @@ private string AssemblyDirectory
{
get
{
var executingAssembly = Assembly.GetExecutingAssembly();
#if NETFRAMEWORK
string codeBase = executingAssembly.CodeBase;
#else
string codeBase = executingAssembly.Location;
#endif
UriBuilder uri = new UriBuilder(codeBase);
string location = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(location);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
Expand Down
9 changes: 2 additions & 7 deletions src/Commands/Pages/ExportPageMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,8 @@ private string AssemblyDirectory
{
get
{
var executingAssembly = Assembly.GetExecutingAssembly();
#if NETFRAMEWORK
string codeBase = executingAssembly.CodeBase;
#else
string codeBase = executingAssembly.Location;
#endif
UriBuilder uri = new UriBuilder(codeBase);
string location = Assembly.GetExecutingAssembly().Location;
UriBuilder uri = new UriBuilder(location);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
Expand Down
Loading