Ошибка экспорта редактирования BCD, когда пользователь не вошел в систему и не запускается через SCCM в качестве системной учетной записи в Powershell

У меня возникла проблема с запуском сценария powershell в качестве системной учетной записи через SCCM при попытке развернуть VHD в системе.

Сценарий пытается выполнить bcdedit /export, однако, когда я запускаю его от своего имени или использую psexec /i /s cmd, а затем запускаю через него powershell, пока я вхожу в систему со своей учетной записью, он работает нормально.

При запуске сценария через SCCM он останавливает сценарий и выдает пользовательскую ошибку, показанную ниже в комментарии вывода журнала, независимо от того, вошел я в систему или нет, что наводит меня на мысль, что система не имеет возможности экспортировать BCD в файл.

Скрипт представлен ниже:

#Set Package Details
$PackageName = "Certiport-Office2013_01.30_86a"

# Set Logging Location
$Global:LogFile = "C:\Logs\$PackageName.log"
$Global:Returnval = 0

##Set Diskpart Commands
$DiskpartAttachVDisk = "SELECT VDISK FILE=D:\Certiport\$PackageName.vhd
ATTACH VDISK"

$DiskpartChangeLetter ="SELECT VDISK FILE=D:\Certiport\$PackageName.vhd
SELECT PARTITION 1
ASSIGN LETTER=V
EXIT"

$DiskpartDetachVDisk = "SELECT VDISK FILE=D:\Certiport\$PackageName.vhd
DETACH VDISK
EXIT"

# Set PKGLocation
$Global:hostinvocation = (Get-Variable MyInvocation).Value
$Global:PkgLocation = if($hostinvocation.MyCommand.path -ne $null) { Split-Path $hostinvocation.MyCommand.path }
       else { (Get-Location).Path  }


######################
## LOGGING FUNCTION ##

##Writes to the log file in C:\Logs
$OSName = (Get-WmiObject -class Win32_OperatingSystem).Caption.trim()
Function Log {
    Param ([string]$LogEntry)
    $TimeStamp = Get-Date -Format "HH:mm:ss"
    Write-Host $TimeStamp - $LogEntry
    Add-Content "$LogFile" -value "$TimeStamp - $LogEntry"
}

##Creates the C:\Logs\PackageName.log or renames it to .lo_ if it already exists
##The .lo_ format is still readable by some live log file readers such as CMTrace and Trace32 and doesn't require being renamed before reading.
$Date = Get-Date
If (test-path $LogFile) {
    Copy-Item -Path $LogFile ($LogFile.TrimEnd("log") + "lo_") -Force
    New-Item -Path "C:\Logs\$PackageName.log" -Force -ItemType File -Value "---Log File---`r`nInstalling = $PackageName`r`nDate = $Date`r`nOperating System = $OSName`r`nOS Architecture = $env:PROCESSOR_ARCHITECTURE`r`n`r`n`r`n---Start Logging---`r`n`r`n"
} else {
    New-Item -Path "C:\Logs\$PackageName.log" -Force -ItemType File -Value "---Log File---`r`nInstalling = $PackageName`r`nDate = $Date`r`nOperating System = $OSName`r`nOS Architecture = $env:PROCESSOR_ARCHITECTURE`r`n`r`n`r`n---Start Logging---`r`n`r`n"
}

######################

Function Check-CriticalError(){
    If($Global:CriticalError -eq $true){
        Log("Critical Error detected! - Script will now Exit")
        $returnval = 1
        Exit($returnval)
    }
}
$Global:CriticalError = $False

Log("######################")
Log("Starting Certiport 2013 VHD Installation")
Log("Source Directory is: $PKGLocation")

# Check that D Drive Exists
If(-Not (Test-Path D:\)){
    Log("ERROR: D Drive does not exist - Exiting")
    $Global:CriticalError = $true
}
Check-CriticalError

# Check Disk Space requirement (40 GB) for D Drive
If(((Get-WmiObject Win32_Volume -Namespace "root\CIMV2" -Filter {DriveLetter = "D:"}).FreeSpace) -lt "21474836480"){
 Log("ERROR: Insufficient Disk Space - 40GB Required - $("{0:N2}" -f (((Get-WmiObject Win32_Volume -Namespace "root\CIMV2" -Filter {DriveLetter = "D:"}).FreeSpace)/1024/1024)) Mb Free - Exiting")
 $Global:CriticalError = $true
}
Check-CriticalError

# Check the Certiport Install Directory exists and create it if not.
If(-Not (Test-Path D:\CertiPort)){
    New-Item -ItemType Directory D:\Certiport | Out-Null
}

# Extract the VHD to the correct directory and perform an MD5 Check OR Verify and validate the state of the currently existing VHD.
$Global:VHDFile = "D:\Certiport\$PackageName.vhd"
$Global:MasterHash = Get-Content $PkgLocation\MD5.txt
If(-Not (Test-Path D:\Certiport\$PackageName.vhd)){
    Log("VHD Does not exist in D:\Certiport - Extracting from Compressed File")
    $ScriptBlock = [Scriptblock]::Create("$PkgLocation\7za.exe x `"$PkgLocation\$PackageName.7z`" -oD:\ -r -aoa")
    Log("Running - `'$ScriptBlock`'")
    $7ZipExtract = Invoke-Command -ScriptBlock $ScriptBlock
    Log("Verifying MD5 Hash")
    $hash = [Security.Cryptography.HashAlgorithm]::Create( "MD5" )
    $stream = ([IO.StreamReader]"$VHDFile").BaseStream 
    $HashCode =  -join ($hash.ComputeHash($stream) | ForEach { "{0:x2}" -f $_ })
    $stream.Close()
    If($MasterHash -ne $HashCode){
        Log("ERROR: Hash Check Failed - `"$MasterHash`" is not `"$HashCode`"")
        Log("ERROR: Source appears corrupted")
        $Global:CriticalError = $true
    }Else{
        Log("Hash Check Successful")
    }
}Else{
    Log("VHD already exists in D:\Certiport - Verifying MD5 Hash")
    $hash = [Security.Cryptography.HashAlgorithm]::Create( "MD5" )
    $stream = ([IO.StreamReader]"$VHDFile").BaseStream 
    $HashCode =  -join ($hash.ComputeHash($stream) | ForEach { "{0:x2}" -f $_ })
    $stream.Close()
    If($MasterHash -ne $HashCode){
        Log("WARNING: Hash Check Failed - `"$MasterHash`" is not `"$HashCode`"")
        Log("Extracting file from source...")
        $ScriptBlock = [Scriptblock]::Create("$PkgLocation\7za.exe x `"$PkgLocation\$PackageName.7z`" -oD:\ -r -aoa")
        Log("Running - `'$ScriptBlock`'")
        $7ZipExtract = Invoke-Command -ScriptBlock $ScriptBlock
        Log("Verifying MD5 Hash")
        $hash = [Security.Cryptography.HashAlgorithm]::Create( "MD5" )
        $stream = ([IO.StreamReader]"$VHDFile").BaseStream 
        $HashCode =  -join ($hash.ComputeHash($stream) | ForEach { "{0:x2}" -f $_ })
        $stream.Close()
        If($MasterHash -ne $HashCode){
            Log("ERROR: Hash Check Failed - `"$MasterHash`" is not `"$HashCode`"")
            Log("ERROR: Source appears corrupted")
            $Global:CriticalError = $true
        }Else{
            Log("VHD Hash Check Successful")
        }
    }Else{
        Log("VHD Hash Check Successful")
    }
}
Check-CriticalError

# Check BCD For any Previous Entry and remove it
$ScriptBlock = [Scriptblock]::Create("bcdedit /v")
$BCDConfig = Invoke-Command -ScriptBlock $ScriptBlock
$BCDItem = (0..($BCDConfig.Count - 1) | Where { $BCDConfig[$_] -like "description*Certiport 2013 Certification*" }) - 3
If($BCDConfig[$BCDItem] -ne $BCDConfig[-3]){
    $BCDIdentifier = (((($BCDConfig[$BCDItem]).Split("{"))[1]).Split("}"))[0]
    Log("Previous entry found - $BCDIdentifier")
    $ScriptBlock = [Scriptblock]::Create("bcdedit /delete ``{$BCDIdentifier``}")
    $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
    If($cmdReturn -eq "The operation completed successfully."){
        Log("Successfully Removed previous Certiport Entry")
    }Else{
        Log("ERROR: Could not remove previous Entry - $cmdReturn")
        $Global:CriticalError = $true
    }
}
Check-CriticalError

# Update Boot Files for UEFI devices and Windows 7

$ScriptBlock = [Scriptblock]::Create("bcdedit /export C:\CertiportVHD-2013-BCD-Backup")
$cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
If($cmdReturn -eq "The operation completed successfully."){
    Set-Content $Env:Temp\CertiportVHD.txt $DiskpartAttachVDisk
    $ScriptBlock = [Scriptblock]::Create("diskpart /s $($Env:Temp)\CertiportVHD.txt")
    $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
    If($cmdReturn -like "*DiskPart successfully attached the virtual disk file.*"){
        Sleep 10
        Set-Content $Env:Temp\CertiportVHD.txt $DiskpartChangeLetter
        $ScriptBlock = [Scriptblock]::Create("diskpart /s $($Env:Temp)\CertiportVHD.txt")
        $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
        If($cmdReturn -like "*DiskPart successfully assigned the drive letter or mount point.*"){
            Log("VHD Successfully Mounted")
            $ScriptBlock = [Scriptblock]::Create("bcdboot.exe V:\Windows")
            $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
            If($cmdReturn -eq "Boot files successfully created."){
                Log("Boot files successfully created")
                Set-Content $Env:Temp\CertiportVHD.txt $DiskpartDetachVDisk
                $ScriptBlock = [Scriptblock]::Create("diskpart /s $($Env:Temp)\CertiportVHD.txt")
                $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
                If($cmdReturn -like "DiskPart successfully detached the virtual disk file."){
                    Log("Successfully Detached the VHD")
                    sleep 10
                    $ScriptBlock = [Scriptblock]::Create("bcdedit /import C:\CertiportVHD-2013-BCD-Backup")
                    $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
                    If($cmdReturn -eq "The operation completed successfully."){
                        Log("Successfully Imported the BCD Backup")
                    }Else{
                        Log("ERROR: Could not restore the BCD Backup - $cmdReturn")
                    }
                }Else{
                    Log("ERROR: Could not detach the VHD - $cmdReturn")
                }
            }Else{
                Log("ERROR: Could not create the boot files - $cmdReturn")
            }
        }Else{
            Log("ERROR: Could not assign the VHD to `"V:`" drive - $cmdReturn")
            $Global:CriticalError = $true
            Check-CriticalError
        }
    }Else{
        Log("ERROR: Could not mount the VHD - $cmdReturn")
        $Global:CriticalError = $true
        Check-CriticalError
    }
}Else{
    Log("ERROR: Could not back up BCD - Quitting immediately to avoid destroying boot order - $cmdReturn")
    $Global:CriticalError = $true
    Check-CriticalError
}

# Configure new BCD Entry For Certiport 2013
Log("Creating BCD Entry")
$ScriptBlock = [Scriptblock]::Create("bcdedit.exe /copy ``{default``} /d `"Certiport 2013 Certification v1.3`"")
$cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
    If($cmdReturn -like "The entry was successfully copied to*"){
        $CertiportGuid = ((($cmdReturn.Split(" "))[6]) -replace ".$") -replace '[{}]',''
        Log("Created new BCD entry - $CertiportGuid")
        $ScriptBlock = [Scriptblock]::Create("bcdedit.exe /set ``{$CertiportGuid``} osdevice vhd=[d:]\Certiport\$PackageName.vhd")
        $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
        If($cmdReturn -like "The operation completed successfully."){
            Log("Successfully changed BCD osdevice")
            $ScriptBlock = [Scriptblock]::Create("bcdedit.exe /set ``{$CertiportGuid``} device vhd=[d:]\Certiport\$PackageName.vhd")
            $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
            If($cmdReturn -like "The operation completed successfully."){
                Log("Successfully changed BCD device")
                $ScriptBlock = [Scriptblock]::Create("bcdedit.exe /timeout 5")
                $cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock
                If($cmdReturn -like "The operation completed successfully."){
                    Log("Successfully changed boot timeout")
                }Else{
                    Log("ERROR: Could not change boot timeout - $cmdReturn")
                }
            }Else{
               Log("ERROR: Could not change BCD device - $cmdReturn")
               $Global:CriticalError = $true 
            }
        }Else{
        Log("ERROR: Could not change BCD osdevice - $cmdReturn")
        $Global:CriticalError = $true
        }
    }Else{
    Log("ERROR: Could not copy BCD Entry for Certiport - $cmdReturn")
    $Global:CriticalError = $true
    }
Check-CriticalError

Log("Certiport Exam VHD Deployed")
exit $returnval

Вывод журнала:

10:28:29 - ######################

10:28:29 - Начало установки VHD Certiport 2013

10:28:29 — Исходный каталог: C:\Windows\SysWOW64\CCM\Cache\00000379.2.System

10:28:29 — VHD уже существует в D:\Certiport — Проверка хэша MD5

10:36:41 - Проверка хэша виртуального жесткого диска прошла успешно

10:36:44 — ОШИБКА: не удалось выполнить резервное копирование BCD — Немедленный выход, чтобы не нарушить порядок загрузки —

10:36:44 - Обнаружена критическая ошибка! - Скрипт теперь выйдет

Мне кажется, что проблема заключается в этом блоке скрипта:

$ScriptBlock = [Scriptblock]::Create("bcdedit /export C:\CertiportVHD-2013-BCD-Backup")
$cmdReturn = Invoke-Command -ScriptBlock $ScriptBlock

Я запускаю код на разных устройствах с теми же результатами. Windows 10 x64, Windows 8.1 x64 и Windows 7 x86/x64.

Запуск powershell v4

Я попытался изменить часть, которая, по моему мнению, была проблемой выше, на:

$App = "bcdedit"
$Arguments = "/export C:\CertiportVHD-2013-BCD-Backup"
$cmdReturn = Start-Process -FilePath $App -ArgumentList $Arguments -Wait -PassThru

Однако это тоже не сработало и дало тот же результат.

Я очень ценю любую оказанную помощь, спасибо заранее.


person Random206    schedule 25.08.2016    source источник
comment
Вы сохраняете результат Invoke-Command в $BCDConfig, но в следующей строке сравниваете $cmdReturn с The operation completed successfully..   -  person Lieven Keersmaekers    schedule 25.08.2016
comment
и между прочим, я бы попробовал запустить procmon во время выполнения вашего скрипта, чтобы получить реальную ошибку (если есть) при выполнении bcdedit   -  person Lieven Keersmaekers    schedule 25.08.2016
comment
@LievenKeersmaekers Спасибо за комментарий, и я приношу свои извинения, это была ошибка с моей стороны при преобразовании этой части сценария обратно в исходную версию. Спасибо за указание на это, я отредактировал его так, как он должен выглядеть. То, что вы видите сейчас, это то, чем управлял SCCM. Мои извинения еще раз.   -  person Random206    schedule 25.08.2016
comment
Согласно вашему журналу, $cmdReturn пусто или равно нулю. Я попытался выполнить команду без прав и попытался выполнить недопустимую команду. Оба по крайней мере возвращают ошибку. Я понятия не имею, как $cmdReturn может быть нулевым. Маршрут procmon, который я предложил, - это то, что я попробую дальше.   -  person Lieven Keersmaekers    schedule 25.08.2016
comment
Спасибо @LievenKeersmaekers. Я попробую завтра, когда вернусь к работе. Однако я не очень хорошо разбираюсь в ProcMon, поэтому, надеюсь, я смогу его понять. Я действительно не использовал его.   -  person Random206    schedule 25.08.2016
comment
Убедитесь, что вы запускаете procmon от имени администратора. По сути, последовательность действий следующая: Запустить Procmon. Выполняйте скрипт, пока не получите ошибку. Остановите Procmon (Ctrl+E) и проанализируйте результаты. Я считаю, что проще всего использовать Ctrl + T для фильтрации интересующего процесса (я полагаю, powershell + дочерние элементы). После этого: Инструменты -> Подсчет вхождений -> Столбец: Результат -> Подсчет. Если повезет, выскочит ошибка (отказано в доступе или что-то в этом роде)   -  person Lieven Keersmaekers    schedule 25.08.2016
comment
Спасибо @LievenKeersmaekers, это действительно должно мне помочь! Я буду держать вас в курсе!   -  person Random206    schedule 25.08.2016
comment
@LievenKeersmaekers К сожалению, мне не удалось найти ошибку с помощью procmon. Любые другие идеи?   -  person Random206    schedule 26.08.2016
comment
Отправить как ответ, чтобы иметь возможность добавлять скриншоты   -  person Lieven Keersmaekers    schedule 26.08.2016


Ответы (2)


Что касается вашего вопроса в комментариях, к сожалению, я не смог найти ошибку с помощью procmon. Есть другие идеи?

Нет, не совсем. Вы говорите, что выполнение команды как системы с использованием psexec работает, и я не вижу никакой разницы в ее запуске под SCCM.

  • Зафиксировал ли procmon начало и конец bcdedit?
  • Какой был код выхода? Можете ли вы опубликовать скриншот или поделиться трассировкой procmon?

трассировка procmon

person Lieven Keersmaekers    schedule 26.08.2016
comment
Спасибо за эту помощь, это было то, что работающий как SCCM по какой-то причине не мог найти bcdedit при простом указании 'bcdedit' в скрипте. Мне пришлось взять копию «bcdedit.exe» и сослаться на нее в папке пакета в кеше, используя «.\bcdedit.exe». - person Random206; 30.08.2016

Оказывается, решение этой проблемы заключалось в том, чтобы взять копию bcdedit и поместить ее в пакет, а не просто указать «bcdedit».

Я обновил ссылки на файл «bcdedit.exe» на «.\bcdedit.exe» через скрипт, поскольку SCCM меняет каталог на корень папки пакета, который он копирует в локальный кеш.

Я не уверен, почему вы можете указать bcdedit при работе от имени пользователя, вошедшего в систему на устройстве, но SCCM при работе от имени системы не может использовать тот же синтаксис, но это исправление сработало.

Спасибо @LievenKeersmaekers за его помощь в том, чтобы помочь мне понять, что файл не может быть найден системой, когда он сказал, что $cmdReturn был пуст, что заставило меня задуматься о том, почему это было, когда он проверил это, и он вернулся значение, независимо от того, ошибочно оно или нет.

Изменить: я также заметил, что то же самое произошло с bcdboot.exe, и мне пришлось скопировать файл в исходные файлы пакета и ссылку .\bcdboot.exe

person Random206    schedule 30.08.2016