找出占用Installer 目录空间的元凶

为什么谈这个话题

今天看到一台windows 7 的计算机,C盘分了50GB,结果installer 目录有47GB,幸亏我对该目录启用过压缩,压缩后实际占用32GB的样子,但也足够大了,已经导致C盘满了,我删了下TEMP目录,清了c:\users\下一些很久没用的用户配置文件,救回2GB出来。但这个Installer目录为什么会占用这么多空间?什么样的靠谱方法可以缩减该尺寸。

你以为我没做过尝试

列举下一些之前尝试的方法,这些方法安全,但是收效甚微。

  • windows 清理程序,即使使用了隐藏的高级功能,但是清理掉的空间不是很多。cmd.exe /c Cleanmgr /sageset:65535 /sagerun:65535 sageset会弹出窗口让选择清理的项目,选择后会保留在注册表中,后面sagerun就会使用这个注册表里存储的选项执行静默的清理。

  • ntfs压缩installer 目录。文章开头已经说了,47GB,压缩到32GB,虽然压缩比率也算较大了,但是其实并没有解决回答为什么增长这么大的问题,其实我觉得应该有办法可以清理。只是不好操作而已,一旦我们必要的信息收集的差不多了,清理这个操作虽然危险,但是应该是可以做的。

不靠谱的清理软件

OK,一般这种状况下会找些流行的专用软件来干这个事,毕竟术业有专攻,第一次使用了WICleanup。

WICleanup列出了冗余文件,而且我的文件清单上有多个文件大小都是一个尺寸,我说这个软件难道是可以算出重复文件的功能,然后把重复文件删掉?!,然后鉴于桌面说用过,我觉得应该至少问题不大吧,看了下目录下有个命令行的版本带-s 可以静默清理,我试了一下,发现清掉30GB多的空间,到installer 目录一看,我就知道坏了,里面的MSP、MSI文件全干掉了。

这个软件我后面看开发时间也是超级古老了,最近还有人发Blog介绍这个工具,而且评论区还有好多人反馈清理了好多.............没发现副作用很大么?。

pjl6523853
爱武侠的程序员2018-04-09 20:37:08#4楼
太感谢博主了!帮我清理了30G!请问博主可以转载嘛

Maxwell_STU
Maxwell_STU2018-03-11 00:45:01#3楼
感谢博主分享,突然就清理出来10G以上的空间,感觉清爽了超级多,压力一下子就小了。

KEVIN_LI_MY
KEVIN_LI_MY2017-11-29 08:59:26#2楼
我清理出了3g,也不少了。

我稍后测试了下用Wicleanup清理过的计算机,控制面板中部分程序的卸载、修复,windows 更新均有问题,主要问题是弹对话框提示找文件。

不靠谱的参考文档

一篇看起很高深的文章指引,又是解构msi文件,又是C++清注册表,主要是一个操作,就是删除HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Products\***********\Patches注册表,然后一股脑删installer目录的下的MSP、MSI文件。

  • 删Patch 注册表这事,会导致系统里对历史更新的记录不存在。在更新软件时会直接安装新软件进行覆盖,而不会清理历史版本,这样可能因为残留导致问题。
  • 第三方软件也放MSP、MSI文件在这个目录,但是产品注册表不一定老老实实的写在上面那个位置,比如Adobe。
  • 还被系统正常使用的MSP、MSI等文件不应当被删除(且看后面的软件如何确认孤立的安装文件,应该有区分的清理,而不是一股脑全删)。

一个看起来可能还靠谱的软件patchCleaner

有了上面教训,我想我得了解下此类软件的原理,然后确定可行后才能使用。首先我参考了微软的员工的解决方法,算是廖胜于无吧,大概意思就是官方仅支持通过卸载软件的方式来清理installer目录,这个Blog在评论区讨论了很多次,但似乎没有什么好的结论,也没有提供太多有价值的信息。

在我访问类似superuser 上的讨论时,我发现了这个软件patchCleaner,为啥说可能还靠谱,因为下面:

  • 最近更新时间 3/03/2016,相对较新
  • 有官方网站还可以访问
  • 有FAQ,还在sourceforge上有个portable版本,尽管我还是没有找到源码。
  • 有原理解释,它说使用WMI来查询程序产品的信息,然后匹对installer 目录下的文件,清理没有在WMI中登记的信息(实际发现应该是用windows installer 的COM对象来查询的相关msi、msp文件的信息)。
  • 我在程序目录找到了一个WMIProducts.vbs 程序,它会使用installer的COM来列出一些程序和组件所使用的MSI、MSP信息。
  • 提供两种选项,DELETE 和MOVE,但是MOVE是推荐的操作,给用户选择且考虑了风险,所以这是我觉得靠谱的地方。我觉得可以提供第三种,COMPRESS(利用ntfs压缩技术可以节省一部分空间)或者第四种,删除前压缩文件到一个文件,这个文件可以后续用于还原。这个文件可以丢在其他地方,比如移动硬盘或者备份的地方。
  • 程序的界面还利用了一些MSI的技术可以显示MSI、MSP文件的详细信息。

准备让数据说话

总结了上面的一些信息,我目前有下列问题,需要让实际的数据说话:

  • 登记在系统中的MSP、MSI文件和孤立的MSP、MSI文件大小汇总占的比率有多大,比如我这个47GB installer的文件夹的情况下孤立的MSP、MSI文件尺寸是什么比率?知道这个信息可以让我们确定如果我们的工具只清理孤立的MSP,MSI的话,这个工具的作用程度可能会有多大。
  • 多个这种非常大的installer 的计算机中,孤立的MSP、MSI文件尺寸是什么比率?有什么特征么? 如果孤立安装文件所占比率较大在多个计算机有共性,那么说明这种工具有普遍需求性。
  • 孤立的MSP、MSI文件的相关信息有哪些共同点,多台计算机上这些匹配出的孤立文件的相关信息有哪些共同点,比如发行者,文件版本等等。是否特定的程序造成了以上问题,如果多个计算机都因为某个软件或者更新导致Installer目录较大,那么我们是否可以通过集中的策略进行控制或者避免?

我的计划:

  • 随机抽取一批windows 计算机,按照操作系统大版本分类。 测试验证了自己的win10
  • 使用patchCleaner提供的VBS文件,搜集多个计算机上的在WMI和MSI数据库中已经登记的MSP、MSI文件列表。 自己写了一个ps 可以统计以上信息
  • 统计每个计算机上的installer 目录下的文件、位置、尺寸,发行者、文件版本、title等信息。 大部分是微软自己的更新
  • 汇总统计每个计算机上的已登记的MSP、MSI文件的总尺寸,孤立的MSP、MSI文件的总尺寸,两者的尺寸比率分布。 个人计算机上孤立的尺寸占了总空间的一半多,而且大部分是一个KB2345678 的更新,我在已安装的更新中搜索了这个KB,发现这个更新是没有装的(这些文件的日期是2015-2017都有,这个补丁应该出来很久了)

自己写了powershell 脚本,按照patchCleaner的思路,自己过滤出孤立的安装文件,这部分孤立文件我后来只过滤出MSI、MSP后缀的文件(这部分文件占用最大)。其他后缀的Installer目录下的文件,我们不去动它(因为可能被引用,比如ICON文件或者EXE等文件)。MSI,MSP文件当中会有一些除了发布者为微软的安装文件,比如Adobe的文件用get-msisummaryinfo 获取不到信息,我们也过滤掉(在PatchCleaner中也默认过滤掉了adobe的安装文件),过滤后的孤立安装文件大概如下图所示。

下面截图中时我的win10 的installer目录的分析情况。使用之前,用过windows 清理程序的高级功能清过。即使使用清理程序清理过,我们也可以看见孤立的文件还有大概4GB,我手动测试了两个安装程序在清理后的工作情况,一个是AMD的显卡软件的,一个是微软的c++ 2012 redistribution,我先把文件从installer 目录剪切走,然后执行卸载或者修复功能,都没有报错或者弹框要文件。

自己的工具?

当上面数据很明了清晰时,我们是否可以写出自己的工具来..大致臆想了自己工具的功能和执行步骤

  • 首先使用cleanmgr 清理一次,看看用户是否还要继续。
  • 使用压缩功能,压缩一次,看看用户是否还要继续。
  • 算出孤立文件的数量(需要验证安全性),优先MOVE (注意juncpoint的兼容问题),再进行删除。我觉得删除的时候是否可以提供选项把删除的文件打包存放在其他位置,出问题时可以用来恢复。
  • 导出孤立文件列表、记录删除的孤立文件清单,用于后续分析。

列下PatchCleaner存在的不足的地方:
1.没有办法导出列表。
2.需要.net framwork4,不便于携带。
3.似乎没有办法可以对筛选后的孤立的文件再做选择性操作。

由于windows 上有PSMSI 这个powershell 模组,所以最开始省去我大部分代码,把主要精力放在测试上(反复考虑后,还是自己写powershell 调用Installer Com 接口的函数用于获取信息,虽然比较困难,全程要用反射功能来操作Installer COM,而且读取MSP文件额度问题已经解决,读MSP时,数据库的Openmode需要指定其他值,这样可以不依赖外部模组)。

$Installer = New-Object -ComObject WindowsInstaller.Installer
$Type = $Installer.GetType()

function Get-MsiProducts {
    $Products = $Type.InvokeMember('Products',"GetProperty",$null,$Installer,$null)
    foreach ($Product In $Products) {
        $hash = @{}
        $hash.ProductCode = $Product
        $Attributes = @('Language','ProductName','PackageCode','Transforms','AssignmentType','PackageName','InstalledProductName','VersionString','RegCompany','RegOwner','ProductID','ProductIcon','InstallLocation','InstallSource','InstallDate','Publisher','LocalPackage','HelpLink','HelpTelephone','URLInfoAbout','URLUpdateInfo')       
        foreach ($Attribute In $Attributes) {
            $hash."$($Attribute)" = $null
        }
        foreach ($Attribute In $Attributes) {
            try {
                $hash."$($Attribute)" = $Type.InvokeMember('ProductInfo',@($Product,$Attribute))
            } catch [System.Exception] {
            }
        }
        if($hash."LocalPackage"){
            if(test-path $hash."LocalPackage"){
                $hash.size=$(get-item $hash."LocalPackage").Length
            }
        }
        New-Object -TypeName PSObject -Property $hash
    }
}

function Get-MsiPatch {
    [cmdletbinding()]
    param(
        $product
    )
    $Patches = $Type.InvokeMember('Patches',@($product))
    foreach ($Patch In $Patches) {
        $hash = @{}
        $hash.ProductCode = $Product
        $hash.PatchCode=$Patch
        $Attributes = @('LocalPackage')     
        foreach ($Attribute In $Attributes) {
            $hash."$($Attribute)" = $null
        }
        foreach ($Attribute In $Attributes) {
            try {
                $hash."$($Attribute)" = $Type.InvokeMember('PatchInfo','GetProperty',@($Patch,$Attribute))
            } catch [System.Exception] {
                #$error[0]|format-list –force
            }
        }
        if($hash."LocalPackage"){
            if(test-path $hash."LocalPackage"){
                $hash.size=$(get-item $hash."LocalPackage").Length
            }
        }
        New-Object -TypeName PSObject -Property $hash
    }
}

function Get-MSIFileInfo {
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory = $true)]$Path     
    )   
    try {
        if(test-path $path){
            $path=get-item $path
            $extension=$path.Extension.ToLower()
            $DBOPENMODE=0
            $TABLENAME='Property'
            if($extension -eq '.msp'){
                $DBOPENMODE=32
                $TABLENAME="MsiPatchMetadata"
            }
            $msiProps = @{}
            $Database = $Type.InvokeMember("OpenDatabase","InvokeMethod",$Null,@($Path.FullName,$DBOPENMODE))
            $Query = "SELECT Property,Value FROM $TABLENAME"
            $View = $Database.GetType().InvokeMember("OpenView",$Database,($Query))
            $View.GetType().InvokeMember("Execute",$View,$null)|Out-Null
            $record=$view.gettype().invokemember("Fetch",$view,$null)   
            # Loop thru the table
            while($record -ne $null) {
                $propName=$null
                $propValue=$null
                $propName=$record.gettype().invokeMember("StringData",$record,1)
                $propValue= $record.gettype().invokeMember("StringData",2)
                $msiProps[$propName] =$propValue
                $record=$view.gettype().invokemember("Fetch",$null)
            }
            $view.gettype().invokemember("Close",$null)|Out-Null
            # Compose a unified object to express the MSI and MSP information
            # MSP  'displayName','ManufacturerName','Description','MoreInfoURL','TargetProductName'
            # MSI 'ProductName','Manufacturer','ProductVersion','ProductCode','UpgradeCode'
            if($extension -eq '.msi'){
                New-Object  -TypeName PSObject -Property @{
                    'displayName'=$msiProps['ProductName']
                    'Manufacturer'=$msiProps['Manufacturer']
                    'Version'=$msiProps['ProductVersion']
                    'PackageCode'=$msiProps['ProductCode']
                    'Description'=$msiProps['Description']
                    'TargetProductName'=$msiProps['TargetProductName']
                    'MoreInfoURL'=$msiProps['MoreInfoURL']
                    'Size'=$path.Length
                    'Path'=$path.FullName
                    'CreationTime'=$path.CreationTime
                }
            }elseif($extension -eq ".msp"){
                New-Object  -TypeName PSObject -Property @{
                    'displayName'=$msiProps['displayName']
                    'Manufacturer'=$msiProps['ManufacturerName']
                    'Version'=$msiProps['BuildNumber']
                    'PackageCode'=$msiProps['ProductCode']
                    'Description'=$msiProps['Description']
                    'TargetProductName'=$msiProps['TargetProductName']
                    'MoreInfoURL'=$msiProps['MoreInfoURL']
                    'Size'=$path.Length
                    'Path'=$path.FullName
                    'CreationTime'=$path.CreationTime
                }
            }       
        }     
    } catch {
        Write-Error $_.Exception.Message
    }
}

function filter_product{
    param(
        $productName
    )

    $PRODUCT_FILTER=@("adobe")

    $r=$PRODUCT_FILTER|?{$productName -like "*$_*"}
    if($r){
        return $true
    }else{
        return $false
    }
}

$products=Get-MsiProducts
$patches=$products|%{Get-MsiPatch -product $_.ProductCode}
$productsHash=@{}
$products|?{$_.LocalPackage}|%{$productsHash.add($_.LocalPackage,$true)}
$patchesHash=@{}
$patches|?{$_.LocalPackage}|%{if(!$patchesHash.ContainsKey($_.localPackage)){$patchesHash.add($_.LocalPackage,$true)}}
$InstallFolder="$($env:SystemRoot)\installer"
$files=dir  -Recurse -Include "*.msi","*.msp" -path $InstallFolder
$Files2=$files|%{
    if($productsHash.ContainsKey($_.FullName)){
        $_|Add-Member -MemberType NoteProperty -Name "installerState" -Value "InstalledProduct"          
    }elseif($patchesHash.ContainsKey($_.FullName)){
        $_|Add-Member -MemberType NoteProperty -Name "installerState" -Value "InstalledPatch"
    }else{
        $_|Add-Member -MemberType NoteProperty -Name "installerState" -Value "Orphaned"
    }
    $_
}

$groups=$files2|group-object -Property "installerState"
$groups|%{
    @{$($_.name)=($_.group|Measure-Object -Property Length -Sum).Sum}
}
$OrphanedFiles=$($groups|?{$_.name -eq 'Orphaned'}).Group
if($OrphanedFiles){
    $ValidOrphanedFiles=($OrphanedFiles|%{
        $item=Get-MSIFileInfo -path $_.FullName;
        if((filter_product $item.displayName) -or (filter_product $item.Manufacturer)){
            # do nothing for this filtered products
        }else{
            $item
        }
    })
    $selectedOrphanedFiles=$ValidOrphanedFiles|select displayName,Manufacturer,Size,Path,CreationTime|Out-GridView -Passthru -Title "select the Orphaned Files to delete"
    if($ValidOrphanedFiles){
        $ValidOrphanedFiles|Export-Csv -Path $PSScriptRoot\ValidOrphanedFiles.$((get-date).ToString('yyyyMMddhhmmss')).csv -NoClobber -NoTypeinformation -Encoding UTF8
    }
    if($selectedOrphanedFiles){
        $selectedOrphanedFiles|Export-Csv -Path $PSScriptRoot\CleanedOrphanedFiles.$((get-date).ToString('yyyyMMddhhmmss')).csv -NoClobber -NoTypeinformation -Encoding UTF8
        # delete code
        #$selectedOrphanedFiles|remove-item -Force
    }
}

需要进一步深挖

使用上面的powershell 脚本在另外一台计算机运行,发现输出如下图,大部分是office的更新,还有4个关于7zip的,所以我又瞄了一眼添加删除程序里的信息。

7z 在添加删除里显示占用空间1.91GB。有点奇怪,我找到这篇参考 还有这篇blog,让我们找找7zip的注册表设置。

######### 我们需要看看HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall\[IdentifyingNumber]\EstimatedSize
######### 通过win32_product 可以获取程序的IdentifyingNumber
Caption           : 7-Zip 9.20 (x64 edition)
Description       : 7-Zip 9.20 (x64 edition)
HelpLink          : http://www.7-zip.org/support.html
HelpTelephone     :
IdentifyingNumber : {23170F69-40C1-2702-0920-000001000000}
InstallDate       : 20180516
InstallDate2      :
InstallLocation   :
InstallSource     : C:\Windows\ccmcache\7\
Language          : 1033
LocalPackage      : C:\Windows\Installer\623473.msi
PackageCache      : C:\Windows\Installer\623473.msi
PackageCode       : {23170F69-40C1-2702-0920-000002000000}
PackageName       : 7z920-x64.msi

然后我们发现7zip的EstimatedSize为2004912,而symantec的EstimatedSize为608916

####### 发现7zip 应该是在注册表中写错数值了,可能是对EstimatedSize单位理解不对,正确单位是1kb的单位。
PS E:\> 594mb/608916
1022.89009321483
PS E:\> 2004912kb/1gb
1.91203308105469
PS E:\> 608916kb/1mb
594.64453125
PS E:\>

原来7Z只是软件BUG导致的尺寸显示问题,那我们接着查补丁相关的问题。

然后我们对孤立的安装文件按照尺寸排序,看看这些文件的详细内容。PSMSI模组的get-msisummaryinfo 工作似乎不是很好,所以我们看不到msp的详细信息(比如我关注的KB编号),没有关系,我们有专门工具Orca可以看MSI、MSP的信息。

以dbeb3e.msp 为例,我们看看orca的显示,切到msipatchMetadata表,这个补丁的KB编号为KB4011169

让我们看看这个KB编号的补丁打了没有,在已安装的更新中搜索这个KB编号,没有找到。

该补丁对应的ProductID 是{643AA346-D215-46E8-89B5-152AD0B7034E},在目标计算机的注册表中搜索这个ProductID 也找不到结果。

那么我们在WSUS看看这个KB4011169补丁的信息。

  • 补丁已经被其他更新取代,需要的计算机只有3台,然而这三台计算机并不包含我现在正在排错的这个计算机。
  • 取代此更新的补丁清单中KB4018389这台计算机上已经安装。
  • 取代KB4011169的补丁链为:
####### 最上面的这个补丁编号是最新的取代该补丁的补丁编号,这个补丁对我这台正在排错的计算机已经安装。
Kb4018389
KB4018330
Kb4018297
KB4011690
Kb4011636
Kb4011279
Kb4011229

这里我找到一个方法可以方便的批量看MSP的信息,而不用图形的Orca 工具。下面脚本遍历所有MSP文件,然后提取MsiPatchMetadata中的displayname属性(包含微软补丁的KB编号)

########## 我这里使用了PSMI模组里的get-msitable
$msps=dir -recurse -path c:\windows\instaler\ -include *.msp 
$xxx=$msps|%{
    $displayname=Get-MSITable -Path $_.fullname -Table MsiPatchMetadata|?{$_.property -eq 'displayname'}|Select-Object -ExpandProperty "value"; 
    [PSCustomObject]@{"displayname"=$displayname;Path=$_.fullname}
}

$xxx|out-gridview

然后在窗口里搜索以上列的取代KB4011169的补丁链KB编号,发现每个历史补丁都在。

这里假设下孤立安装文件产生的主要原因是因为补丁取代导致,那我们验证下这个测试:

  1. 把KB4011169的对应的孤立文件dbeb3e.msp 移走,然后因为取代该补丁的最新补丁KB为Kb4018389,且在正在排错的这个计算机上有安装。那我们测试卸载这个Kb4018389补丁,看是否有问题。没有问题,因为这个补丁是补丁替代链上的最后一个补丁

  2. 卸载KB4018389后,其替代的补丁KB4018330是否会在已安装的更新列表中呢?(windows 是否会还原上一个版本的补丁?)是的,第一次卸载这个补丁花了4-5小时,重启后发现前一个版本的补丁在已安装的更新中。

  3. 如果我们卸载了KB4018389,那么对应Kb4018389现在对应的MSP文件,c:\windows\installer\dd988e.msp 是否会被删掉?是的,该文件在补丁卸载后在installer目录不再存在。

  4. 假设卸载了KB4018389,我们又通过运行windows更新又把它更新上了,那么新安装的KB4018389对应的MSP文件名字是否有变化?名字有变化,变为dc993.msp

  5. 把KB4018389(kb4011196对应的最新补丁)的前一个版本补丁(KB4018330)对应的安装文件ddf836.msp 删除,那我们测试卸载这个Kb4018389补丁,看是否有问题。同时注意KB4018330或Kb4018297是否会出现在已安装更新里。 KB4018389 可以正常卸载没有问题,KB4018330由于我们把其对应的补丁的MSP文件移走所以在已安装的更新中看不到。但是我看到了KB4018297在已安装的更新当中,有意思的发现。另外这个是否如果检查更新的话,你会看到有个两个更新可用(KB4018389,KB4018330)看来我得加一个测试

  6. 使用脚本删除找出的所有孤立的MSP、MSI文件,然后卸载KB4018297,看看是否报错?(这个时候应该没有任何历史版本可以用了),另外卸载后检查更新看看更新会检查到几个?(涉及到历史补丁在WSUS的维护工作)。 卸载不会报错,检查更新后发现有三个补丁,KB4018389,KB4018330 同时存在,我选择更新KB4018389和另外一个,看看重启后是否还有让更新KB4018330,重启后发现不再提示KB4018330的更新

写在最后

  • 我想已经有足够的信息去弄明白为什么Installer目录会变得这么大了,因为windows 保留了多个补丁的历史替代版本,当你卸载一个补丁A时,它还原上一个版本的补丁B,如果这个B还有上一个版本C,当你再卸载B时,它会还原C。

  • 系统应该有信息保留着补丁链的信息,因此如果找到这些信息的存放位置,可以构建一个工具来保留特定数目的补丁链,比如只保留一个历史版本。

  • 按照现有的计算机的情况分析,instaler的空间大部分是被windows的 更新所占用,特别是补丁有多个替代版本时。其中office 补丁最多。

  • Windows disk Cleaner 有清理历史补丁的功能,但是不确定它的逻辑,比如我自己的windows 10 机器,使用了磁盘清理功能后,还有较多的历史补丁存在。如果要弄明白windows disk cleaner的机制,可能还要做很多的实验才有结果。

  • powershell 使用的PSMSI的cmdlet get-msisummaryinfo 感觉在不同操作系统上显示的信息不同,不是太可靠。所以自己最后决定还是写powershell 函数来提取关键信息,最终完成单脚本不再使用PSMSI模组。改良后的脚本最后发现了大量的SilverLight 更新残留包,占了大概12GB,因为之前使用的get-msiSummaryinfo 获取silverlight 相关补丁信息时,获取不到标题。所以会被认为是adobe的包跳过。

  • 看来我也得包装下标题,免得被审核的小编歧视...................

放个做好的exe文件吧

脚本已经转成可自解压执行的exe文件 [下载](http://down.51cto.com/data/2447291)

找出占用Installer 目录空间的元凶的更多相关文章

  1. ios – Xcode警告:“没有处理文件的规则”和“找不到目录”

    重命名我的项目文件夹后,我收到以下错误消息:什么可能出错?解决方法关于第一个警告,您可以在项目设置中的“构建阶段”中检查XCode,即头文件不会出现在“编译源”列表中.

  2. 你如何将xcode项目转换为cocoapod?

    我有一段代码,我发现我在多个不同的项目中重复使用,所以我想把它变成一个cocoapod并使用私人cocoapod仓库.我的问题是如何将xcode项目设置为cocoapod?它应该是一个静态库还是一个带有appdelegate的空“项目”?

  3. ios – 如何将文件添加到主包的/ Library / Sounds目录中?

    根据Apple’sdocumentation,/Library/Sounds中的声音文件将在尝试播放声音时由系统搜索.如何将声音文件添加到此文件夹?适用于iOS的正确文档应为here总之,您只需将声音文件作为应用程序包的非本地化资源添加到项目中.

  4. ios – 资产目录与文件夹参考:何时使用其中一个?

    我可以将文件放入Assets.xcassets,或者我可以将文件放入文件夹引用.我何时会选择一个而不是另一个?

  5. ios – 从icloud备份中限制sqlite-wal和sqlite-shm

    我是第一次使用coredata,我必须从文档目录中的iCloud备份限制sqlitedb文件,我使用下面的代码完成了它//阻止iCloud备份文档目录文件夹现在我不明白的是,我们还需要从icloud备份中限制sqlite-wal和sqlite-shm文件,如果是,那么如何从icloud备份限制sqlite-wal和sqlite-shm文件我想要一个解决方案,而无需从文档目录文件夹中更改sqlitedb位置…

  6. iOS:如何从文档目录中删除具有特定扩展名的所有现有文件?

    当我更新我的iOS应用程序时,我想删除Documents目录中的任何现有sqlite数据库.现在,在应用程序更新时,我将数据库从软件包复制到文档目录,并通过附加软件包版本来命名它.因此,在更新时,我还想删除可能存在的任何旧版本.我只是希望能够删除所有sqlite文件,而无需循环浏览并查找以前版本的文件.是否有任何方法可以对removeFileAtPath:方法进行通配符?解决方法那么,你想要删除所有*.sqlite文件?

  7. .dylib在Debug中链接,在XCode中找不到适用于iPhone的版本

    所以我已经将libxml2.2.dylib库包含在我的iPhoneXCode项目中,以创建一些Xml和XPath解析实用程序.当我编译并运行在模拟器和设备的调试模式时,我没有问题,但是,当我切换到发布模式我得到…

  8. ios – 从文件目录加载UIImage

    我正在尝试从文件目录加载一个UIImage,并将其设置为UIImageView,如下所示:但是,每当我尝试以上,图像从不加载.该图像在Documents/MyAppCustomDirectory/school.png中.以上是否正确从该目录加载?我也尝试了其他几个:UIImageimageWithContentsOfFile,以及基于SO响应的其他方法.解决方法要获取您应该使用的文档目录:我不太清

  9. Xcode 6 / iOS 8模拟器数据和软件包文件夹脚本

    随着xcode6的最新更改,它看起来像.app文件和文档文件夹不再驻留在iPhone模拟器目录中的同一个文件夹中.以前,我们可以访问目录和.app文件但在Xcode6中,模拟器目录是完全不同的:和其中CryptNumber1,2和3都不同.有没有办法找到包含.app文件的文件夹后,我可以得到相应的文件夹?我有这个麻烦.为什么…?

  10. ios – 上传符号文件

    我该如何解决?

随机推荐

  1. static – 在页面之间共享数据的最佳实践

    我想知道在UWP的页面之间发送像’selectedItem’等变量的最佳做法是什么?创建一个每个页面都知道的静态全局变量类是一个好主意吗?

  2. .net – 为Windows窗体控件提供百分比宽度/高度

    WindowsForm开发的新手,但在Web开发方面经验丰富.有没有办法为Windows窗体控件指定百分比宽度/高度,以便在用户调整窗口大小时扩展/缩小?当窗口调整大小时,可以编写代码来改变控件的宽度/高度,但我希望有更好的方法,比如在HTML/CSS中.在那儿?

  3. 使用Windows Azure查询表存储数据

    我需要使用特定帐户吗?>将应用程序部署到Azure服务后,如何查询数据?GoogleAppEngine有一个数据查看器/查询工具,Azure有类似的东西吗?>您可以看到的sqlExpressintance仅在开发结构中,并且一旦您表示没有等效,所以请小心使用它.>您可以尝试使用Linqpad查询表格.看看JamieThomson的thispost.

  4. windows – SetupDiGetClassDevs是否与文档中的设备实例ID一起使用?

    有没有更好的方法可以使用DBT_DEVICEARRIVAL事件中的数据获取设备的更多信息?您似乎必须指定DIGCF_ALLCLASSES标志以查找与给定设备实例ID匹配的所有类,或者指定ClassGuid并使用DIGCF_DEFAULT标志.这对我有用:带输出:

  5. Windows Live ID是OpenID提供商吗?

    不,WindowsLiveID不是OpenID提供商.他们使用专有协议.自从他们的“测试版”期结束以来,他们从未宣布计划继续它.

  6. 如果我在代码中进行了更改,是否需要重新安装Windows服务?

    我写了一个Windows服务并安装它.现在我对代码进行了一些更改并重新构建了解决方案.我还应该重新安装服务吗?不,只需停止它,替换文件,然后重新启动它.

  7. 带有双引号的字符串回显使用Windows批处理输出文件

    我正在尝试使用Windows批处理文件重写配置文件.我循环遍历文件的行并查找我想要用指定的新行替换的行.我有一个’函数’将行写入文件问题是%Text%是一个嵌入双引号的字符串.然后失败了.可能还有其他角色也会导致失败.如何才能使用配置文件中的所有文本?尝试将所有“在文本中替换为^”.^是转义字符,因此“将被视为常规字符你可以尝试以下方法:其他可能导致错误的字符是:

  8. .net – 将控制台应用程序转换为服务?

    我正在寻找不同的优势/劣势,将我们长期使用的控制台应用程序转换为Windows服务.我们为ActiveMQ使用了一个叫做java服务包装器的东西,我相信人们告诉我你可以用它包装任何东西.这并不是说你应该用它包装任何东西;我们遇到了这个问题.控制台应用程序是一个.NET控制台应用程序,默认情况下会将大量信息记录到控制台,尽管这是可配置的.任何推荐?我们应该在VisualStudio中将其重建为服务吗?我使用“-install”/“-uninstall”开关执行此操作.例如,seehere.

  9. windows – 捕获外部程序的STDOUT和STDERR *同时*它正在执行(Ruby)

    哦,我在Windows上:-(实际上,它比我想象的要简单,这看起来很完美:…是的,它适用于Windows!

  10. windows – 当我试图批量打印变量时,为什么我得到“Echo is on”

    我想要执行一个简单的批处理文件脚本:当我在XP中运行时,它给了我预期的输出,但是当我在Vista或Windows7中运行它时,我在尝试打印值时得到“EchoisOn”.以下是程序的输出:摆脱集合表达式中的空格.等号(=)的两侧可以并且应该没有空格BTW:我通常在@echo关闭的情况下启动所有批处理文件,并以@echo结束它们,所以我可以避免将代码与批处理文件的输出混合.它只是使您的批处理文件输出更好,更清洁.

返回
顶部