This commit is contained in:
pucketm2016 committed 2026-08-09 17:33:11 -05:00
1 parent d27811254d
commit 582d0e635d
35 files changed
+2944 -1203

No files matched your search

+2 -2
View File
@@ -33,9 +33,9 @@ export function destroy(req, res) {
res.status(204).end();
}
export function createRdpSession(req, res) {
export async function createRdpSession(req, res) {
try {
res.status(201).json(createRdpLaunchSession(req.params.id, req.user.id));
res.status(201).json(await createRdpLaunchSession(req.params.id, req.user.id));
} catch (error) {
res.status(error.statusCode || 400).json({ error: error.message });
}
+31
View File
@@ -1,5 +1,8 @@
import { analyzeInstaller, listInstallerTechnologies } from '../services/installerIntelService.js';
import { buildDetectionRule } from '../services/detectionRuleService.js';
import { buildRecipeArtifacts, getRecipe, searchRecipes } from '../services/recipeService.js';
import { createPsadtProfile, createIntuneDeployment } from '../models/psadtModel.js';
import { createApplication } from '../models/applicationModel.js';
export function installerTypes(req, res) {
res.json(listInstallerTechnologies());
@@ -18,3 +21,31 @@ export function detection(req, res) {
res.status(400).json({ error: error.message });
}
}
export function recipes(req, res) {
res.json(searchRecipes(req.query?.q || ''));
}
export function applyRecipe(req, res) {
const recipe = getRecipe(req.params.id);
if (!recipe) return res.status(404).json({ error: 'Recipe not found' });
try {
const artifacts = buildRecipeArtifacts(recipe, {
visibility: req.body?.visibility || 'personal',
groupId: req.body?.groupId || null
});
const userId = req.user.id;
const profile = createPsadtProfile(artifacts.profile, userId);
const application = artifacts.application ? createApplication(artifacts.application, userId) : null;
const deployment = createIntuneDeployment({
...artifacts.deployment,
profileId: profile.id,
applicationId: application?.id || null
}, userId);
res.status(201).json({ recipeId: recipe.id, profile, deployment, application });
} catch (error) {
res.status(400).json({ error: error.message });
}
}
+106 -2
View File
@@ -26,10 +26,13 @@ import { computeDrift } from '../services/intuneDriftService.js';
import { canPublish } from '../services/graphRbac.js';
import { cloneForNextVersion, setRingIntent } from '../services/intunePromotionService.js';
import { parseIntunewin } from '../services/intunewinParser.js';
import { createAssetFromUpload, getAssetFile } from '../models/assetModel.js';
import { createAssetFromUpload, getAssetFile, listLinkedAssets } from '../models/assetModel.js';
import { validatePsadtScript, validationRuleCatalog } from '../services/psadtValidator.js';
import { planPsadtMigration } from '../services/psadtMigrationService.js';
import { basenameAny, path } from '../utils/pathUtils.js';
import { ENTRY_SCRIPT_NAME, buildZip, planPackageLayout, resolveSetupFile } from '../services/packageBuilder.js';
import { renderDatasheet } from '../services/packageDocService.js';
import { loadApplication } from '../models/applicationModel.js';
import { basenameAny, path, sanitizeFileName } from '../utils/pathUtils.js';
export function catalog(req, res) {
res.json(getPsadtCatalog());
@@ -434,6 +437,24 @@ export function intuneGraphAudit(req, res) {
res.json(listGraphAudit({ deploymentId: deployment.id }));
}
// Auto-generated package datasheet: render a deployment (plus its linked PSADT
// profile and catalog application) to Markdown or HTML. Pure render lives in
// packageDocService. ?format=md|html (default md); ?download=1 forces a file.
export function intuneDatasheet(req, res) {
const deployment = loadIntuneDeployment(req.params.id, req.user.id);
if (!deployment) return res.status(404).json({ error: 'Intune deployment not found' });
const profile = deployment.profileId ? loadPsadtProfile(deployment.profileId, req.user.id) : null;
const application = deployment.applicationId ? loadApplication(deployment.applicationId, req.user.id) : null;
const doc = renderDatasheet({ deployment, profile, application }, { format: req.query?.format });
const fileName = `${sanitizeFileName(doc.title || 'package', 'package').replace(/\.[^.]+$/, '')}-datasheet.${doc.extension}`;
res.type(doc.contentType);
if (req.query?.download === '1' || req.query?.download === 'true') {
res.set('Content-Disposition', `attachment; filename="${fileName}"`);
}
res.send(doc.content);
}
export function index(req, res) {
res.json(listPsadtProfiles(req.user.id));
}
@@ -467,6 +488,89 @@ export function render(req, res) {
res.json(rendered);
}
// Assemble a complete, portable PSADT v4 package from a profile: render the
// Invoke-AppDeployToolkit.ps1 entry script, lay out Files/SupportFiles/Assets
// from the profile's linked Asset Library items, zip it, and ingest the zip into
// the Asset Library. With { build: true } also hand the assembled tree to the
// .intunewin builder. Pure layout + zip logic lives in packageBuilder.
export async function packageProfile(req, res) {
const rendered = renderPsadtProfile(req.params.id, req.user.id);
if (!rendered) return res.status(404).json({ error: 'PSADT profile not found' });
const links = listLinkedAssets('psadt_profile', req.params.id, req.user.id);
const layout = planPackageLayout(links);
const warnings = [...layout.warnings];
const zipEntries = [
{ path: ENTRY_SCRIPT_NAME, data: Buffer.from(rendered.script, 'utf8') },
...layout.directories.map((dir) => ({ path: dir, isDirectory: true }))
];
for (const asset of layout.assets) {
const fileRef = getAssetFile(asset.assetId, req.user.id);
if (!fileRef) {
warnings.push(`Asset "${asset.name || asset.assetId}" is no longer accessible and was skipped.`);
continue;
}
zipEntries.push({ path: asset.packagePath, data: fs.readFileSync(fileRef.filePath) });
}
const includedFiles = zipEntries.filter((entry) => !entry.isDirectory && entry.path !== ENTRY_SCRIPT_NAME);
const stageDir = path.join(config.toolsDir, '_package', rendered.profile.id);
fs.rmSync(stageDir, { recursive: true, force: true });
fs.mkdirSync(stageDir, { recursive: true });
const baseName = sanitizeFileName(`${rendered.profile.name || 'psadt'}-package`, 'psadt-package').replace(/\.[^.]+$/, '');
const zipPath = path.join(stageDir, `${baseName}.zip`);
const zipBuffer = buildZip(zipEntries);
fs.writeFileSync(zipPath, zipBuffer);
const asset = createAssetFromUpload(
{ path: zipPath, originalname: `${baseName}.zip`, mimetype: 'application/zip', size: zipBuffer.length },
{ name: `${rendered.profile.name} package`, visibility: rendered.profile.visibility, groupId: rendered.profile.groupId, packagePath: `${baseName}.zip` },
req.user.id
);
const manifest = {
script: ENTRY_SCRIPT_NAME,
directories: layout.directories,
files: layout.assets.map((item) => ({ packagePath: item.packagePath, role: item.role, name: item.name })),
fileCount: includedFiles.length
};
let intunewin = null;
if (req.body?.build) {
const setupFile = resolveSetupFile(layout, req.body?.setupFile);
const availability = builderAvailability();
if (!setupFile) {
warnings.push('Skipped .intunewin build: no installer in the package. Link an installer asset or pass setupFile.');
} else if (!availability.available) {
warnings.push(`Skipped .intunewin build: ${availability.reason}`);
} else {
const sourceFolder = path.join(stageDir, 'source');
for (const entry of zipEntries) {
const dest = path.join(sourceFolder, entry.path);
if (entry.isDirectory) { fs.mkdirSync(dest, { recursive: true }); continue; }
fs.mkdirSync(path.dirname(dest), { recursive: true });
fs.writeFileSync(dest, entry.data);
}
try {
const { outputFile } = await buildIntuneWin({ sourceFolder, setupFile, outputDir: path.join(stageDir, '_out') });
const stats = fs.statSync(outputFile);
const outName = basenameAny(outputFile, 'package.intunewin');
intunewin = createAssetFromUpload(
{ path: outputFile, originalname: outName, mimetype: 'application/octet-stream', size: stats.size },
{ name: `${rendered.profile.name} intunewin`, visibility: rendered.profile.visibility, groupId: rendered.profile.groupId, packagePath: outName },
req.user.id
);
manifest.setupFile = setupFile;
} catch (error) {
warnings.push(`.intunewin build failed: ${error.message}`);
}
}
}
res.status(201).json({ asset, manifest, warnings, intunewin, builder: builderAvailability() });
}
export function rules(req, res) {
res.json(validationRuleCatalog());
}