Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6b672c5
Ran NuGetRestoreForceEvaluateAllSolutions.ps1
JunielKatarn Apr 3, 2026
4defcf2
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Apr 3, 2026
3072c6f
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn Apr 30, 2026
ea678bd
Define installNuGetPackages task; Install pwsh 7.6.1
JunielKatarn Apr 30, 2026
2efbed6
Replace powershell.exe with pwsh.exe
JunielKatarn Apr 30, 2026
fd7192d
Change files
JunielKatarn Apr 30, 2026
47d5250
CI: Ensure NuGet is availabe before `yarn install`
JunielKatarn Apr 30, 2026
0b2e3c9
Add NuGetAuthenticate before yarn install
JunielKatarn May 1, 2026
0756f21
Do not download PowerShell on CI/CD environments
JunielKatarn May 1, 2026
a09c51a
Fix just-task JS syntax
JunielKatarn May 1, 2026
d7257de
Add dotnet-tools manifest
JunielKatarn May 1, 2026
dc22ba3
Fix pwsh.exe tool path
JunielKatarn May 1, 2026
6c44358
Resolve dotnet-tools.json path
JunielKatarn May 1, 2026
58791b9
Use dotnet for `nuget locals`
JunielKatarn May 1, 2026
69a6031
Use PWSH in nuget-restore-task.js
JunielKatarn May 1, 2026
582447c
Break dependency loop in just-task
JunielKatarn May 1, 2026
0a07510
Update other ADO tasks
JunielKatarn May 1, 2026
9a279f5
Update @react-native-windows/automation
JunielKatarn May 1, 2026
32f3b10
Rename findPwsh as findPowerShell
JunielKatarn May 1, 2026
9c8d4f7
Drop powershell export from commandWithProgress
JunielKatarn May 1, 2026
a88f21c
Change files
JunielKatarn May 1, 2026
b7b454e
Add README for find-dotnet-tools
JunielKatarn May 1, 2026
16ddee9
Add README to package.json
JunielKatarn May 1, 2026
d0d0d03
Adjust newline
JunielKatarn May 1, 2026
fa3d334
Run task `installDotnetTools` only when not in CI
JunielKatarn May 1, 2026
357c415
Install .NET tools in CI
JunielKatarn May 1, 2026
7bffc70
Merge branch 'pwsh' of github.com:jurocha-ms/react-native-windows int…
JunielKatarn May 1, 2026
5e25b9b
Install .NET on prepare-js-env
JunielKatarn May 1, 2026
209c16d
Do not install .NET tools on CI
JunielKatarn May 1, 2026
028d93b
Quote powershell path
JunielKatarn May 1, 2026
31b87eb
fix yarn lint
JunielKatarn May 1, 2026
457c33b
Ran NuGetRestoreForceEvaluateAllSolutions.ps1
JunielKatarn May 1, 2026
b620764
Restore deleted lock files
JunielKatarn May 1, 2026
95ff042
Add missing dependency
JunielKatarn May 1, 2026
f8a60de
Merge branch 'pwsh' of https://github.com/jurocha-ms/react-native-win…
JunielKatarn May 1, 2026
8d9d229
Use -Command for compatible PWSH calls
JunielKatarn May 2, 2026
acb7e23
Use -Command pwsh argument
JunielKatarn May 2, 2026
898c20e
Correct runPowerShellScriptFunction cmd escaping
JunielKatarn May 2, 2026
2c9c518
Quote pwsh command in runPowerShellScriptFunction
JunielKatarn May 2, 2026
e08f93c
Remove quotes
JunielKatarn May 3, 2026
8ed6050
Quote remaining findPowerShell() calls for execSync
JunielKatarn May 3, 2026
683e3db
Add temporary diagnostic commands
JunielKatarn May 4, 2026
1b741d2
Diagnostics
JunielKatarn May 4, 2026
b241cbc
Diagnostics
JunielKatarn May 4, 2026
87ee497
Diagnostics
JunielKatarn May 4, 2026
17f0668
Add runPowerShellScriptFunction argument to delegate Appx-related com…
JunielKatarn May 4, 2026
f30780a
Remove diagnostics steps
JunielKatarn May 4, 2026
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
2 changes: 1 addition & 1 deletion .ado/jobs/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ jobs:
condition: and(failed(), eq(variables.StartedFabricTests, 'true'))
continueOnError: true

- powershell: |
- pwsh: |
if (Test-Path "packages/e2e-test-app-fabric/test/__image_snapshots__/__diff_output__") {
Write-Host "##vso[task.setvariable variable=DiffOutputExists]true"
}
Expand Down
2 changes: 1 addition & 1 deletion .ado/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function ensureNuGet(toolsPath) {
ensureDir(toolsPath);
console.log(`Downloading nuget.exe to: ${localNuGet}`);
execSync(
`powershell.exe -NoLogo -NoProfile -Command ` +
`pwsh.exe -NoLogo -NoProfile -Command ` +
`"[Net.ServicePointManager]::SecurityProtocol = ` +
`[Net.SecurityProtocolType]::Tls12; ` +
`Invoke-WebRequest -Uri 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' ` +
Expand Down
1 change: 1 addition & 0 deletions .ado/templates/install-SDK.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ steps:
targetType: filePath
filePath: vnext\Scripts\Install-WindowsSdkISO.ps1
arguments: ${{ parameters.sdkVersion }}
pwsh: true
displayName: 'Install Insider SDK (${{ parameters.sdkVersion }})'
condition: ne('', '${{ parameters.sdkVersion }}')
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Migrate to PowerShell 7",
"packageName": "@react-native-windows/automation",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Upgrade to PowerShell 7",
"packageName": "@react-native-windows/cli",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Migrate to PowerShell 7",
"packageName": "@react-native-windows/find-dotnet-tools",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Upgrade to PowerShell 7",
"packageName": "react-native-windows",
"email": "julio.rocha@microsoft.com",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions packages/@react-native-windows/automation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@react-native-windows/automation-channel": "0.0.0-canary.1052",
"@react-native-windows/find-dotnet-tools": "0.0.0-canary.1",
"@react-native-windows/fs": "^0.0.0-canary.72",
"@typescript-eslint/eslint-plugin": "^8.36.0",
"@typescript-eslint/parser": "^8.36.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import chalk from 'chalk';
import {spawnSync, spawn, ChildProcess} from 'child_process';
import fs from '@react-native-windows/fs';
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';
import path from 'path';
import readlineSync from 'readline-sync';

Expand Down Expand Up @@ -250,7 +251,7 @@ export default class AutomationEnvironment extends NodeEnvironment {
if (this.breakOnStart) {
readlineSync.question(
chalk.bold.yellow('Breaking before tests start\n') +
'Press Enter to resume...',
'Press Enter to resume...',
);
}

Expand Down Expand Up @@ -335,8 +336,15 @@ function resolveAppName(appName: string): string {
}

try {
const packageFamilyName = spawnSync('powershell', [
`(Get-AppxPackage -Name ${appName}).PackageFamilyName`,
const useAppxCompatibility = !!process.env.TF_BUILD;
const escapedAppName = appName.replace(/'/g, "''");
const packageFamilyNameCommand = useAppxCompatibility
? `& { Import-Module Appx -UseWindowsPowerShell; (Get-AppxPackage -Name '${escapedAppName}').PackageFamilyName }`
: `(Get-AppxPackage -Name '${escapedAppName}').PackageFamilyName`;
const packageFamilyName = spawnSync(findPowerShell(), [
'-NoProfile',
'-Command',
packageFamilyNameCommand,
])
.stdout.toString()
.trim();
Expand Down
1 change: 1 addition & 0 deletions packages/@react-native-windows/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@react-native-windows/codegen": "0.0.0-canary.133",
"@react-native-windows/find-dotnet-tools": "0.0.0-canary.1",
"@react-native-windows/fs": "^0.0.0-canary.72",
"@react-native-windows/package-utils": "^0.0.0-canary.98",
"@react-native-windows/telemetry": "^0.0.0-canary.133",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import type {
HealthCheckCategory,
HealthCheckInterface,
} from '@react-native-community/cli-doctor/build/types';
import {powershell} from '../../utils/commandWithProgress';
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';
import {HealthCheckList} from './healthCheckList';

const powershell = findPowerShell();

export function getHealthChecks(): HealthCheckCategory[] | undefined {
// #8471: There are known cases where the dependencies script will error out.
// Fail gracefully if that happens in the meantime.
Expand Down Expand Up @@ -76,7 +78,7 @@ function getHealthChecksUnsafe(): HealthCheckCategory[] | undefined {
};
},
runAutomaticFix: async ({loader, logManualInstallation}) => {
const command = `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -Check ${id}`;
const command = `"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -Check ${id}`;
try {
const {exitCode} = await execa(command, {stdio: 'inherit'});
if (exitCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@

import {execSync} from 'child_process';
import path from 'path';
import {powershell} from '../utils/commandWithProgress';
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';
import {HealthCheckList} from '../commands/healthCheck/healthCheckList';

const powershell = findPowerShell();

test('Verify list of health checks aligns with rnw-dependencies', async () => {
const rnwDepScriptPath = path.join(
path.dirname(
Expand All @@ -20,7 +22,7 @@ test('Verify list of health checks aligns with rnw-dependencies', async () => {
);

const rnwDeps = execSync(
`${powershell} -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -NoPrompt -ListChecks`,
`"${powershell}" -ExecutionPolicy Unrestricted -NoProfile "${rnwDepScriptPath}" -NoPrompt -ListChecks`,
{stdio: 'pipe'},
);
const deps = rnwDeps.toString().trim().split('\n');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CodedErrors,
CodedErrorType,
} from '@react-native-windows/telemetry';
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';

function setSpinnerText(spinner: ora.Ora, prefix: string, text: string) {
text = prefix + spinnerString(text);
Expand Down Expand Up @@ -47,18 +48,23 @@ export function newSpinner(text: string) {
return ora(options).start();
}

export const powershell = `${process.env.SystemRoot}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
const powershell = findPowerShell();

export async function runPowerShellScriptFunction(
taskDescription: string,
script: string | null,
funcName: string,
verbose: boolean,
errorCategory: CodedErrorType,
useAppxCompatibility = false,
) {
try {
const printException = verbose ? '$_;' : '';
const importScript = script ? `Import-Module "${script}"; ` : '';
const importAppx = useAppxCompatibility
? 'Import-Module Appx -UseWindowsPowerShell; '
: '';
const importScript = script ? `Import-Module '${script}'; ` : '';
const powershellCommand = `${importAppx}${importScript}try { ${funcName} -ErrorAction Stop; $lec = $LASTEXITCODE; } catch { $lec = 1; ${printException} }; exit $lec`;
await commandWithProgress(
newSpinner(taskDescription),
taskDescription,
Expand All @@ -67,7 +73,8 @@ export async function runPowerShellScriptFunction(
'-NoProfile',
'-ExecutionPolicy',
'RemoteSigned',
`${importScript}try { ${funcName} -ErrorAction Stop; $lec = $LASTEXITCODE; } catch { $lec = 1; ${printException} }; exit $lec`,
'-Command',
`&{${powershellCommand}}`,
],
verbose,
errorCategory,
Expand All @@ -88,7 +95,7 @@ export function commandWithProgress(
errorCategory: CodedErrorType,
) {
return new Promise<void>((resolve, reject) => {
const spawnOptions: SpawnOptions = verbose ? {stdio: 'inherit'} : {};
const spawnOptions: SpawnOptions = verbose ? { stdio: 'inherit' } : {};

if (verbose) {
spinner.stop();
Expand Down
17 changes: 14 additions & 3 deletions packages/@react-native-windows/cli/src/utils/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import {
newSpinner,
commandWithProgress,
runPowerShellScriptFunction,
powershell,
} from './commandWithProgress';
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';
import * as build from './build';
import {
BuildConfig,
Expand Down Expand Up @@ -183,8 +183,9 @@ function getWindowsStoreAppUtils(options: RunWindowsOptions) {
'powershell',
'WindowsStoreAppUtils.psm1',
);
const powershell = findPowerShell();
execSync(
`${powershell} -NoProfile Unblock-File '${windowsStoreAppUtilsPath}'`,
`"${powershell}" -NoProfile -Command { Unblock-File '${windowsStoreAppUtilsPath}' }`,
);
popd();
return windowsStoreAppUtilsPath;
Expand Down Expand Up @@ -359,6 +360,7 @@ export async function deployToDesktop(
config: Config,
buildTools: MSBuildTools,
) {
const useAppxCompatibility = !!process.env.TF_BUILD;
const windowsConfig: Partial<WindowsProjectConfig> | undefined =
config.project.windows;
const slnFile =
Expand Down Expand Up @@ -391,6 +393,7 @@ export async function deployToDesktop(
'EnableDevMode',
verbose,
'EnableDevModeFailure',
useAppxCompatibility,
);

const appPackageFolder = getAppPackage(options, projectName);
Expand All @@ -403,6 +406,7 @@ export async function deployToDesktop(
`Uninstall-App ${appName}`,
verbose,
'RemoveOldAppVersionFailure',
useAppxCompatibility,
);

const script = glob.sync(
Expand All @@ -415,6 +419,7 @@ export async function deployToDesktop(
`Install-App "${script}" -Force`,
verbose,
'InstallAppFailure',
useAppxCompatibility,
);
} else {
// Deploy from layout
Expand Down Expand Up @@ -442,6 +447,7 @@ export async function deployToDesktop(
`Install-AppDependencies ${appxManifestPath} ${appPackageFolder} ${options.arch}`,
verbose,
'InstallAppDependenciesFailure',
useAppxCompatibility,
);
await build.buildSolution(
buildTools,
Expand All @@ -456,8 +462,12 @@ export async function deployToDesktop(
}
}

const escapedAppName = appName.replace(/'/g, "''");
const appFamilyNameCommand = useAppxCompatibility
? `& { Import-Module Appx -UseWindowsPowerShell; (Get-AppxPackage -Name '${escapedAppName}').PackageFamilyName }`
: `(Get-AppxPackage -Name '${escapedAppName}').PackageFamilyName`;
const appFamilyName = execSync(
`${powershell} -NoProfile -c $(Get-AppxPackage -Name ${appName}).PackageFamilyName`,
`"${findPowerShell()}" -NoProfile -Command "${appFamilyNameCommand}"`,
)
.toString()
.trim();
Expand Down Expand Up @@ -488,6 +498,7 @@ export async function deployToDesktop(
`Start-Locally ${appName} ${args}`,
verbose,
'AppStartupFailure',
useAppxCompatibility,
);
} else {
newInfo('Skip the step to start the app');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import {
newSpinner,
newSuccess,
newError,
powershell,
} from './commandWithProgress';
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';
import {execSync} from 'child_process';
import {BuildArch, BuildConfig} from '../commands/runWindows/runWindowsOptions';
import {findLatestVsInstall} from './vsInstalls';
Expand Down Expand Up @@ -317,7 +317,7 @@ export default class MSBuildTools {
'Eval-MsBuildProperties.ps1',
);

let command = `${powershell} -ExecutionPolicy Unrestricted -NoProfile "${msbuildEvalScriptPath}" -SolutionFile '${solutionFile}' -ProjectFile '${projectFile}' -MSBuildPath '${this.msbuildPath()}'`;
let command = `"${findPowerShell()}" -ExecutionPolicy Unrestricted -NoProfile "${msbuildEvalScriptPath}" -SolutionFile '${solutionFile}' -ProjectFile '${projectFile}' -MSBuildPath '${this.msbuildPath()}'`;

if (propertyNames && propertyNames.length > 0) {
command += ` -PropertyNames '${propertyNames.join(',')}'`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
extends: ['@rnw-scripts'],
parserOptions: {tsconfigRootDir : __dirname},
};
2 changes: 2 additions & 0 deletions packages/@react-native-windows/find-dotnet-tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lib/
lib-commonjs/
49 changes: 49 additions & 0 deletions packages/@react-native-windows/find-dotnet-tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# @react-native-windows/find-dotnet-tools

Helpers to locate .NET-based tools (e.g. PowerShell) restored via `dotnet tool restore` or
available on PATH.

Used to resolve tool paths consistently across local development and CI
environments.

## Usage

Add the package as a dependency:

```json
{
"dependencies": {
"@react-native-windows/find-dotnet-tools": "<version>"
}
}
```

### findPowerShell

Locates a PowerShell executable by checking, in order:

1. A `dotnet-tool`-restored copy of `pwsh.exe` (skipped in CI builds)
2. `pwsh.exe` on the system PATH
3. The built-in Windows PowerShell as a last resort

```js
import {findPowerShell} from '@react-native-windows/find-dotnet-tools';

const pwsh = findPowerShell();
// e.g. "C:\\Users\\user\\.nuget\\packages\\PowerShell\\7.6.1\\tools\\net10.0\\any\\win\\pwsh.exe"
```

### getNugetGlobalPackagesFolder

Returns the path to the global NuGet packages folder by checking, in order:

1. The `NUGET_PACKAGES` environment variable
2. The output of `dotnet nuget locals global-packages --list`
3. The default `~/.nuget/packages` location

```js
import {getNugetGlobalPackagesFolder} from '@react-native-windows/find-dotnet-tools';

const packagesDir = getNugetGlobalPackagesFolder();
// e.g. "C:\\Users\\user\\.nuget\\packages"
```
Loading
Loading