SUP: Поиск недокачанных обновлений

Относительно недавно вышел Service Pack 3 для Office 2007, проблем с его распространением через SCCM/WSUS оказалось больше, чем ожидалось. То, что для него требуется языков больше, чем необходимо администратору пестрит множество блогов, но очень мало кто описывает решение данной проблемы, в основном описывая какие нужно языки докачать… Но это решение подходит тем, кто использует англоязычную версию. Что же делать тем, кто использует русскую, украинскую и еще какую либо версию, отличающуюся от западной? Изначально мною проблема решалась следующим образом — я, как и все, закачал английскую, русскую, испанскую и т.д. версии, как было описано в других блогах, найденных в гугле. На серверах всё прошло успешно и я расслабился, но ко мне обратились коллеги, которые сказали, что на некоторых рабочих станциях обновление висит на загрузке 50%. Такая ситуация возникает не в первый раз и не только с этим обновлением, я решил разобраться с ней более детально. Первое, что я сделал, это открыл многим не безызвестную утилиту — SCCM Client Center и увидел, что клиент не может скачать несколько пакетов обновлений. Имея только guid’ы обновлений, я написал скрипт, который отображал мне полную информацию об обновлении, в том числе Bundle update («связка» обновлений, в которую входит данное обновление), ссылка на скачивание, имя файла и самое главное — язык.

Get-SMSUpdateInfo.ps1:

Param(
[Parameter(Mandatory = $False)][guid]$Identity,
[Parameter(Mandatory = $False)][string]$SMSServer
)
If (!($SMSServer)) {$SMSServer = '.'}
$NameSpace = 'ROOT\sms\Site_' + (gwmi -ComputerName $SMSServer -NameSpace 'ROOT\Sms' -Class SMS_ProviderLocation).SiteCode
If ($Identity) {$ContentFiles = gwmi -NameSpace $NameSpace -Class SMS_CIToContent -ComputerName $SMSServer -Filter "ContentUniqueId='$Identity'"}
Else {$ContentFiles = gwmi -NameSpace $NameSpace -Class SMS_CIToContent -ComputerName $SMSServer}
	ForEach ($ContentFile in $ContentFiles) {
		$CiId=$ContentFile.CI_ID
		$Update = gwmi -NameSpace $NameSpace -Class SMS_SoftwareUpdate -ComputerName $SMSServer -Filter "CI_ID='$CiId'"
		If ($Update){
			$ContentId = $ContentFile.ContentID
			$ContentLocales = $ContentFile.ContentLocales
			$Content = gwmi -NameSpace $NameSpace -Class SMS_CIContentFiles -ComputerName $SMSServer -Filter "ContentID='$ContentId'"
			$Locale = gwmi -NameSpace $NameSpace -Class SMS_CategoryInstance -ComputerName $SMSServer -Filter "CategoryInstance_UniqueId='$ContentLocales'"
			$Properties = @{'Identity' = $ContentFile.ContentUniqueId;
							'FileName' = $Content.FileName;
							'FileSize' = $Content.FileSize;
							'Language' = $Locale.LocalizedCategoryInstanceName
							'DownloadUrl' = $Content.SourceURL
							'Bundle' = $Update.LocalizedDisplayName
							'ArticleId' = $Update.ArticleId
							'InfoUrl' = $Update.LocalizedInformativeURL
							'Description' = $Update.LocalizedDescription
							}
			$Object = New-Object -TypeName PSObject -Property $Properties
		}
		Write-Output -InputObject $Object
	}

Данный скрипт необходимо запускать от имени администратора сервера SCCM. После того, как вы скопировали скрипт, сохраните его в удобной для вас локации и введите команду .\Get-SMSUpdateInfo.ps1 -Identity ‘тут guid злополучного обновления’. Если скрипт запускается не на сервере SCCM, то укажите -SMSServer ‘имя сервера SCCM’.

Реклама

OSD: Запрос имени компьютера

Скорее всего многие уже читали на сайте itband.ru статью от Алексея Тараненко об именовании компьютеров. И многие уже используют кто что нашел — MDT 2010 с модифицированным загрузочным образом, всяческие скрипты, либо просто добавляет переменную OSDComputerName для коллекции All Unknown Computers. Если честно, то ниодно решение из существовавших на тот момент меня не устраивали, в результате чего была написана довольно простая форма для ввода имени компьютера. По сути, это порезанная веб форма из MDT 2010 и также, как и в MDT 2010 загрузочный образ должен поддерживать запуск HTA приложений. Рекомендую запускать ее не раньше шага ‘Patitioning Disk 0’, т.к. последовательность задач может завершиться с ошибкой при попытке загрузить приложение локально.

PromptSystemName.HTA:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<!--

' // ***************************************************************************
' //
' // Prompt System Name HTA OSD Task Sequence for Unknown Computers
' //
' // ***************************************************************************

-->

<title>Please type computer name here</title>

<HTA:APPLICATION ID="oHTA"
SCROLL="no"
SELECTION="no"
INNERBORDER="no"
BORDER="normal"
SINGLEINSTANCE="no"
SYSMENU="yes"
MAXIMIZEBUTTON="no"
MINIMIZEBUTTON="no"
RESIZEABLE="no"
/>

<style type="text/css">
H1 {font-size: 14pt; color: #1370AB;}
.Maintext {font-size: 10pt; color: #333333;}
.ErrMsg {color: red;background-color: #ffff80; display: none;}
.Larger {font-size: 12pt;}
</style>

<script language="vbscript">

Set oTaskSequence = CreateObject ("Microsoft.SMS.TSEnvironment")
 sTSMachineName = ucase(oTaskSequence("_SMSTSMachineName"))
If left(sTSMachineName,6) <> "MININT" then
 if sTSMachineName <> "MINWINPC" then window.Close
end if
 intwinhsize = 380
 intwinlsize = 420
 intHorizontal = 800
 intVertical = 600
 intLeft = (intHorizontal - intwinlsize) / 2
 intTop = (intVertical - intwinhsize) / 2
 window.resizeTo intwinlsize,intwinhsize
 window.moveTo intLeft, intTop

Set ProgressUI = CreateObject("Microsoft.SMS.TsProgressUI")
 ProgressUI.CloseProgressDialog

Function ValidateComputerName

If Len(ComputerName.value) > 15 then
 InvalidChar.style.display = "none"
 TooLong.style.display = "inline"
 NoName.style.display = "none"
 ValidateComputerName = false
 ButtonNext.disabled = true
ElseIf Len(ComputerName.value) = 0 then
 InvalidChar.style.display = "none"
 TooLong.style.display = "none"
 NoName.style.display = "inline"
 ValidateComputerName = false
 ButtonNext.disabled = true
ElseIf IsValidComputerName (ComputerName.Value) then
 ValidateComputerName = TRUE
 InvalidChar.style.display = "none"
 NoName.style.display = "none"
 TooLong.style.display = "none"
 ButtonNext.disabled = false
Else InvalidChar.style.display = "inline"
 TooLong.style.display = "none"
 ValidateComputerName = false
 NoName.style.display = "none"
 ButtonNext.disabled = true
End If

End function

Function IsValidComputerName (ComputerName)
 Dim regEx
 Set regEx = New RegExp
 regEx.Pattern = "[^a-zA-Z0-9\-\_]"
 IsValidComputerName = not regEx.Test (ComputerName) and len(ComputerName) <= 15
End function

sub ButtonFinishClick
 if buttonNext.Disabled then
 exit sub
 else
 oTaskSequence("OSDComputerName") = UCase(document.all.ComputerName.value)
 Window.Close
 end if
end sub

sub ButtonCancelClick
 If Msgbox("Are you sure you want to cancel?",4,"Cancel wizard?") = vbYES Then
 window.Close
 end If
end sub

sub onload
 document.all.ComputerName.value = sTSMachineName
 document.all.ComputerName.focus
 document.all.ComputerName.select
end sub

sub KeyHandler
 if window.event.KeyCode = 27 then
 ButtonCancelClick
 elseif window.event.KeyCode = 13 then
 ButtonFinishClick
 end if
end sub

</script>
</head>

<body onload=onload onkeydown="KeyHandler">
<table cellpadding="0" cellspacing="0" border="0" width="100%" height="100%">
 <tr>
 <td>
 <H1>Configure the computer name</H1>
 <span>

<p>Choose a name for your PC that will help you identify it on your network, if you have one. Each computer on your network must have a unique name.</p>
 <P>You can name your computer based on its owner or location, for example "DAVID" or "XYZLAB123." In order
 for your computer to appear on a network, its name cannot be longer than 15 characters or contain any
 spaces or characters aside from the numbers 0-9, the letters A-Z and a-z, and hyphens.</P>

<p><span>Computer n<u>a</u>me:</span>
 <input type=text id=ComputerName name=ComputerName size=15 language=vbscript onpropertychange=ValidateComputerName AccessKey=A /></p>
 <p>&nbsp;
 <label id=NoName>* Required (MISSING)</label>
 <label id=InvalidChar>Letters, Numbers & Dashes only!</label>
 <label id=TooLong>Maximum of 15 characters!</label>
 </p>
 </td>
 </tr>
 <tr>
 <td class="CommandArea" id="RightWizardButtons" align="right">
 <button accesskey=N id=buttonNext onclick=ButtonFinishClick language=vbscript>Fi<U>n</U>ish</button>
 <button accesskey=C id=buttonCancel onclick=ButtonCancelClick language=vbscript><U>C</U>ancel</button>
 </td>
 </tr>
</table>
</body>
</html>

OSD: Оффлайн обновление WIM образов

Думаю, многие как-то пытались решить вопрос по актуализации обновлений в существующих образах.

 
Существует несколько способов:

  • Build and Capture
  • Использование функционала SCCM 2012 уже сегодня, обновляя образы на тестовых стендах
  • Использование WAIK
  • Использование утилиты DISM и скриптов

Как раз последний способ и будет описан в данной статье. Этот скрипт создаст функцию Add-WimPackages. Данная функция использует утилиту DISM и Imagex, которые входят в комплект WAIK 2, поэтому скрипт будет проверять наличие WAIK 2.0. Итак, что делает данный скрипт? После запуска данного скрипта вам необходимо будет ввести команду «Add-WimPackages -Path <Полный путь до WIM файла> -PackagesPath <Путь до каталога с обновлениями>». В качестве пути к каталогу с обновлениями необходимо задать папку, куда SCCM загружает обновления. Если вы не используете SCCM, как точку обновлений, а используете WSUS, то вы можете указать каталог с вручную скачанными обновлениями в формате .msu или .cab. Скрипт необходимо запускать с привелегироваными правами.

add-wimPackagescmdlet.ps1


function Add-WimPackages {
param([parameter(Mandatory = $true)][string]$Path, [parameter(Mandatory = $true)][string]$PackagesPath, [string]$MountDir)

# Set variables

$tools = "$env:ProgramFiles\Windows AIK\Tools\$env:PROCESSOR_ARCHITECTURE"
$imagex = $tools + '\imagex.exe'
$dism = "$env:windir\system32\dism.exe"
$wid = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$prp = new-object System.Security.Principal.WindowsPrincipal($wid)
$adm = [System.Security.Principal.WindowsBuiltInRole]::Administrator
$IsAdmin = $prp.IsInRole($adm)

# Check conditions

if (!($IsAdmin)) {write-host 'Insufficient access rights.' -foregroundcolor red; return}
if (!(Test-Path $Path)) {write-host 'Path to WIM file is incorrect.' -foregroundcolor red; return}
if (!(Test-Path $PackagesPath)) {write-host 'Path to Packages is incorrect.' -foregroundcolor red; return}
if (!(gwmi -Namespace root\cimv2 -class WIN32Reg_AddRemovePrograms | ? {($_.DisplayName -eq 'Windows Automated Installation Kit') -and ($_.Version -ge '2.0.0.0')})) {write-host 'You must install Windows AIK 2.0 or later.' -foregroundcolor red; return}
if (!(Test-Path $dism)) {write-host 'DISM utility not found.' -foregroundcolor red; return}
if (!(Test-Path $imagex)) {write-host 'IMAGEX utility not found.' -foregroundcolor red; return}
[xml]$imagexxml = &$imagex /INFO $Path /XML
if ($imagexxml.toString() -ne '#document') {write-host 'WIM file is incorrect.' -foregroundcolor red; return}
if (!($MountDir)) {$MountDir = "$env:SystemDrive\"}
if (!(Test-Path $MountDir)) {write-host 'Mount directory is incorrect.' -foregroundcolor red; return}

# Process script

$MountDir = $MountDir + "\Mount\"
$Packages = gci $PackagesPath -Recurse | ? {($_.Name -like "*.cab") -or ($_.Name -like "*.msu")}
foreach ($image in $imagexxml.SelectNodes("WIM/IMAGE")) {
[string]$index=$image.index
$Major = $image.SelectSingleNode("WINDOWS/VERSION/MAJOR")."#text"
$Minor = $image.SelectSingleNode("WINDOWS/VERSION/MINOR")."#text"
if ($Major -eq $null) {write-host "Image is not Microsoft Windows operating system" -foregroundcolor yellow}
if ($Major -lt '6') {write-host "Image index $index is cannot be updated in offline" -foregroundcolor yellow}
else {
write-host "Updating Image: $index`nImage name: "$image.SelectSingleNode("NAME")."#text" -ForegroundColor green
$Mount = "$MountDir\$index\"
$job = New-Item -Path $Mount -ItemType Directory -force
$job = &$dism /Mount-Wim /WimFile:"$path" /Index:$index /MountDir:$mount /English
if ($image.SelectSingleNode("WINDOWS/ARCH")."#text" -eq '0') {$arch = 'x86'}
elseif ($image.SelectSingleNode("WINDOWS/ARCH")."#text" -eq '9') {$arch = 'x64'}
else {$arch = 'ia64'}
$job = &dism /Image:"$Mount" /Get-Packages /English
$exists = $job -split "~" -split "_" | ? {$_ -like 'KB*'}
foreach ($Package in $Packages) {
$PackagePath = $Package.FullName
$Package = $Package.Name
if (!($exists -contains ($Package -split "-" | ? {$_ -like "KB*"})) -and ($Package -like "*windows$major.$minor-kb*$arch.*")) {
$job = &$dism /Image:"$Mount" /Add-Package /PackagePath:"$PackagePath" /English
if ($job -match '0x800f08') {write-host "The package $Package is not applicable to this image or cannot be added in offline." -ForegroundColor yellow}
if ($job -match 'successfully') {write-host "The package $Package applied succesfully." -ForegroundColor Green}
}
else {Write-Host "The package $Package is not applicable to this image or already exists." -ForegroundColor yellow}
}
write-host "Unmounting image." -ForegroundColor green
$job = &dism /Commit-Wim /MountDir:"$Mount" /English
$job = &dism /Unmount-Wim /MountDir:"$Mount" /commit /English
if ($job -contains "The operation completed successfully.") {Remove-Item "$Mount" -Recurse; write-host "The image $index updated successfully." -ForegroundColor green}
else {write-host "The image $index is not unmounted.`nStop processing image." -ForegroundColor red; return}
}
}
if (Test-Path "$MountDir") {Remove-Item "$MountDir" -Recurse}
Write-host "All images successfully updated." -ForegroundColor green
}

Данный способ не является наилучшим и уступает Build & Capture, т.к. есть обновления, которые можно установить только в онлайне и работает только для операционных систем Windows Vista SP2 и выше. Также не стоит забывать, что есть еще обновления в формате .exe, но всё же лучше, чем ничего.