Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 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
0943a29
Use execFileSync instead of execSync in deploy.ts
JunielKatarn May 5, 2026
e1f3cfd
Remove querystrings from FS paths
JunielKatarn May 5, 2026
abf7349
Suppress warning when using `-UseWindowsPowerShell`
JunielKatarn May 5, 2026
b1ed90a
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn May 5, 2026
0a90d24
Revert "Remove querystrings from FS paths"
JunielKatarn May 5, 2026
6c46c73
Use pipe semantics for Get-AppxPackage in resolveAppName
JunielKatarn May 6, 2026
a3eb65b
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn May 6, 2026
7d45510
Hard-fail if PowerShell 7 cannot be found.
JunielKatarn May 6, 2026
df2a184
Improve missing pwsh.exe error message
JunielKatarn May 6, 2026
5472ece
Add package resolution pipeline test
JunielKatarn May 6, 2026
8c20407
UseWindowsPowerShell for package resolution test
JunielKatarn May 6, 2026
10099d0
Try to read package Microsoft.PrintDialog
JunielKatarn May 6, 2026
a5b016a
Replace stderr redirection with -WarningAction
JunielKatarn May 6, 2026
2f5a23a
Replace stderr redirection with -WarningAction
JunielKatarn May 6, 2026
6e70148
Print sample app names on appWindow error
JunielKatarn May 6, 2026
c8f2dec
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn May 6, 2026
8c10bc4
Add more diagnostics
JunielKatarn May 6, 2026
0f41c50
Add missing -WarningAction SilentlyContinue
JunielKatarn May 7, 2026
926459e
Revert "Add more diagnostics"
JunielKatarn May 7, 2026
106ab19
Revert "Print sample app names on appWindow error"
JunielKatarn May 7, 2026
3ef3061
Remove CI probing tasks
JunielKatarn May 7, 2026
e323f54
Revert "Use pipe semantics for Get-AppxPackage in resolveAppName"
JunielKatarn May 7, 2026
1c003d3
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn May 7, 2026
75878db
Remove unused escaping logic
JunielKatarn May 7, 2026
ea00939
Re-introduce deleted .lock.json
JunielKatarn May 7, 2026
c960d39
Merge branch 'main' of github.com:microsoft/react-native-windows into…
JunielKatarn May 7, 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,14 @@ function resolveAppName(appName: string): string {
}

try {
const packageFamilyName = spawnSync('powershell', [
`(Get-AppxPackage -Name ${appName}).PackageFamilyName`,
const useAppxCompatibility = !!process.env.TF_BUILD;
const packageFamilyNameCommand = useAppxCompatibility
? `& { Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; (Get-AppxPackage -Name '${appName}').PackageFamilyName }`
: `(Get-AppxPackage -Name '${appName}').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 -WarningAction SilentlyContinue; '
: '';
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
30 changes: 22 additions & 8 deletions packages/@react-native-windows/cli/src/utils/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* @format
*/

import {spawn, execSync, SpawnOptions} from 'child_process';
import {spawn, execFileSync, SpawnOptions} from 'child_process';
import fs from '@react-native-windows/fs';
import http from 'http';
import path from 'path';
Expand All @@ -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,9 +183,12 @@ function getWindowsStoreAppUtils(options: RunWindowsOptions) {
'powershell',
'WindowsStoreAppUtils.psm1',
);
execSync(
`${powershell} -NoProfile Unblock-File '${windowsStoreAppUtilsPath}'`,
);
const powershell = findPowerShell();
execFileSync(powershell, [
'-NoProfile',
'-Command',
`& { Unblock-File '${windowsStoreAppUtilsPath}' }`,
]);
popd();
return windowsStoreAppUtilsPath;
}
Expand Down Expand Up @@ -359,6 +362,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 +395,7 @@ export async function deployToDesktop(
'EnableDevMode',
verbose,
'EnableDevModeFailure',
useAppxCompatibility,
);

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

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

const appFamilyName = execSync(
`${powershell} -NoProfile -c $(Get-AppxPackage -Name ${appName}).PackageFamilyName`,
)
const appFamilyNameCommand = useAppxCompatibility
? `& { Import-Module Appx -UseWindowsPowerShell -WarningAction SilentlyContinue; (Get-AppxPackage -Name '${appName}').PackageFamilyName }`
: `(Get-AppxPackage -Name '${appName}').PackageFamilyName`;
const appFamilyName = execFileSync(findPowerShell(), [
'-NoProfile',
'-Command',
appFamilyNameCommand,
])
.toString()
.trim();

Expand Down Expand Up @@ -488,6 +501,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/
50 changes: 50 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,50 @@
# @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

Throws an error if `pwsh.exe` cannot be found.

```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